《C++ 习题与解析》笔记
C++基础: 基础数据类型,简单输入输出,流程控制语句,函数与编译预处理,数组,结构体,指针与引用
C++面向对象部分: 类与对象,构造和析构函数,继承与派生,友元,虚函数,静态成员,运算符重载
Chapter-1 C++语言概述
位运算操作符
单目:~(按位求反)
双目:&(与)、 |(或)、 ^(按位异或)
移位运算符
<< (左移): 左移是将一个二进制数按指定的位数向左移位,移掉的位被丢弃,右边移出的空位一律补0
>> (右移): 右移是将一个二进制数按指定的位数向右移位,移掉的位被丢弃,左边移出的空位一律补0,或补符号位
逗号运算符
d1, d2, d3, d4: 计算一个逗号表达式的值时,从左至右依次计算各个表达式的值,最后计算的一个表达式的值和类型便是整个逗号表达式的值和类型
二维数组指针表示
//输出对应的值的三种方法
int b[2][3];
a. *(*(b+i)+j)
b. *(b[i]+j)
c. *(&b[0][0]+3*i+j)
const 位置与指向问题
//都定义了a是一个常整数,不能修改a的值
int const a = 1;
const int a = 1;
int b[] = {1,2,3}
const int *a = b; // a为指向常整数的指针,a为数组b的首地址,a指向的整数不可修改,不能通过a来修改其指向的值,但可以通过b来修改数组值,而且a可以指向其他数组
int * const a = b; // a为指向整数的常指针,a为数组b的首地址,a指向的整数可以修改,但是指针a不可修改,不能让a指向其他数组,也不能执行a++语句
指针传址操作
void swap()
{
int a = 1,b=2;
int& a1 = a;
int& b1 = b;
swap1(a,b); //传变量的值(在方法内操作的只是temp,改变不了实际ab的值)
swap2(&a,&b); //传变量的地址(直接操作内存地址的值,最粗暴的方式 )
swap3(a1,b1); //传变量的引用 (变量与变量的别名,它们的地址是同一个,直接操作ab的值 )
cout<< a << "," << b;
}
void pointer2(){
int a=10;
int& b =a;
int *q;
q = &a;
//修改a的值
*q = 2;
printf("%d\n",*q);
changeValue(q);//不行
changeValue2(&q);//(ok)传二级指针修改值
changeValue3(b);//(ok)直接传引用修改值
printf("%d\n",*q);
}
void changeValue(int *p){//传进来的p是a的地址,或者说p只是一个普通变量,这个变量存放了a的地址
int b=100;
p = &b;//因为p是一个变量,所以,这边的p是个副本_p,对副本进行操作,并不会改变实际a的值
}
void changeValue2(int **p){//传进来的是p的地址,不是a的地址!!
int b=100;
*p = &b;//这边虽然也有一个副本_p,但是没有影响,因为*p取到p地址的值,也就是a的地址,然后修改其值
}
void changeValue3(int& p){
p = 100;//引用理解为是对指针进行了封装,使用起来简单
}
Chapter-1 C++语言概述(错题)
- [例1.3]
int i=1;
void max()
{ int i = i;}
//main()里的i是未定义值,是局部变量,当执行int i=i;时,先在内存中为局部变量i分配内存空间,其值是不确定的,然后执行i=i,由于值不确定,所以main()里的i是一个未定义值
[例1.10]
在一定条件下,指针可以进行两种运算,即两个指针可以相减,一个指针可以与一个适当的整数
相加减,但不能进行两个指针的相加运算[例1.16]
struct
{
float f; //4 byte
char c; //1 byte
int adf[3]; //12byte;
}x;
cout << sizeof(x) << endl; // output: 20byte;
//默认以4字节为对齐单位,f占4个字节,c占一个字节,adf占12个字节,总共17个字节。按4字节为对齐单位时,要选择4的倍数,即为20个字节。
- [例1.24]
swicth(){ case _A_: 表达式。 } //在A处:case之后只能用常量表达式,不能用实型表达式
- [例1.30]
for(int i=0,j=10; i=j=10; i++,j--); //执行次数:无限次。 return i=j=10 -> true
- [例1.36]
若要定义一个只允许本源文件所有函数使用的全局变量,应该使用的存储类别是:static
c++规定,全局变量分为两种:extern和static型,前者的作用域为整个程序,后者的作用域为定义该变量的文件
- [例1.43]
void main()
{
int b = 3;
int arr[] = {6,7,8,9,10};
int *ptr = arr;
*(ptr++) += 123;
printf(" %d, %d\n",*ptr,*(++ptr)); //output: 8 8 ;
}
对于printf语句,其参数从右向左运算,第一个是*(++ptr),第二个是*ptr
- [例1.57]
func(x)的功能是将x转化为2进制,求其中含有的
1的个数
int func(int x)
{
int countx = 0;
while(x)
{
countx++;
x = x & (x-1); //每一次计算对应2进制的一个1就会变成0 直到所有1都变为0循环结束
}
return countx;
}
Chapter-2 类和对象
定义类注意点
类体中不允许对所定义的数据成员进行初始化
用new创建的类对象都是匿名对象,必须用一个指针指向它,通过该指针对这个对象进行操作 class A{..}; A *p = new A;
拷贝构造函数
用一个已知对象来初始化一个被创建的同类对象
class Sample{
Sample(const Sample &s){...}
}
void main(){
Sample s1, s2(s1);
}
使用情况如下:
- 明确表示由一个对象初始化另一个对象时
- 当对象作为函数实参传递给函数形参时
- 当对象作为函数返回值时
调用析构函数
析构函数自动调用的情况
- 对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用
- 使用new运算符动态创建一个对象后,当使用delete运算符释放它时,delete将会自动调用析构函数
析构函数的调用顺序与构造函数相反
void main(){
Sample s1,s2(10); //析构函数调用时先释放s2,再释放s1
}
深拷贝/浅拷贝
浅拷贝:
当两个对象之间进行复制,复制完成后,还共享某些资源(内存空间),其中一个对象销毁会影响另一个对象,这种对象之间的复制称为对象浅复制
class Sample{
private:
int no;
char *pname;
public:
Sample(const Sample &s){
no = s.no;
pname = s.pname;
}
}
void main(){
Sample s1, s2(s1);
}
深拷贝:
当两个对象之间进行复制,复制完成后,他们不会共享任何资源,其中一个对象的销毁不会影响另一个对象
种对象之间的复制称为对象深复制
class Sample{
private:
int no;
char *pname;
public:
Sample(const Sample &s){
no = s.no;
// pname = s.pname;
pname = new char[strlen(s.pname)+1];
strcpy(pname,s.pname);
}
}
void main(){
Sample s1, s2(s1);
}
类成员函数指针
class Sample
{
int m,n;
public:
void setm(int i){ m=i; }
void setn(int i){ n=i; }
}
void main(){
void(Sample::*pfun)(int); //类成员函数指针
Sample a;
pfun=Sample::setm; //指向Sample类成员函数setm;
(a.*pfun)(10);
pfun=Sample::setn; //指向Sample类成员函数setn;
(a.*pfun)(20);
}
程序中类Sample的setm/setn成员函数必须具有相同的返回类型(这里均为void),而且为public时才能这样使用
子对象
构造函数的执行次序是:(析构函数相反)
- 以子对象在类中说明的顺序调用子对象初始化列表中列出的各构造函数
A(参数表):obj(参数表2){ 函数体; }
- 然后执行函数体
class B1{
public:
B1(){cout << "B1:Constructor" << endl; }
~B1(){cout << "B1:Constructor" << endl; }
};
class B2{
public:
B2(){cout << "B2:Constructor" << endl; }
~B2(){cout << "B2:Constructor" << endl; }
};
class B3{
public:
B3(){cout << "B3:Constructor" << endl; }
~B3(){cout << "B3:Constructor" << endl; }
};
class A
{
B1 b1;
B2 b2;
B3 b3;
pubilc:
A():b3(),b2(),b1(){ cout<< "A:Constructor" << endl; }
~A() { cout << "A:Destructor" << endl; }
};
void main()
{
A a;
}
//Output
B1:Constructor
B2:Constructor
B3:Constructor
A:Constructor
B3:Destructor
B2:Destructor
B1:Destructor
常类型
- 常对象:
类名 const 对象名 / const 类名 对象名
- 声明为常对象的同时必须被初始化,并从此不能改写对象的数据成员。
- 常对象只能调用类的常成员函数或类的静态成员函数
const Sample a(10);
- 常对象成员:
- 常成员函数
数据类型 函数名(参数表) const;
- 常成员函数
- 常成员函数不更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数
- 如果将一个对象声明为常对象,则通过该常对象
只能调用它的常成员函数
,而不能调用其他成员函数 const
关键字可以被用于参与对重载函数的区分:void print();
void print() const; //正确的重载
- 常对象成员:
只能通过初始化列表对该数据成员进行初始化
class Sample
{
const int n;
public:
Sample(int i):n(i){}
}
- 常对象成员:
Chapter-2 类和对象(错题)
[例2.32]
已知f1(int)
是类A的公有成员函数,p是指向成员函数f1的指针,为其赋值时正确的是:p=A::f1
[例2.35]
class Test
{
public:
Test(){}
~Test(){cout << '#'; }
}; void main(){
Test temp[2], *pTemp[2]; // 执行程序输出:2个'#'号
}
Test *pTemp[2]只建立了两个Test对象指针的数组,并没有创建Test对象,不会调用构造函数和析构函数
[例2.37]
class point
{
public:
point() { cout<<"C"; }
~point() { cout<<"D"; }
}; void main()
{
point *ptr;
point A,B;
point *ptr_point = new point[3];
//output: CCCCCDD
}
没有delete ptr_point 因此没有调用对应的析构函数
[例2.55]
class B
{
int x,y;
public:
B() {x=y=0; cout << "Constructor1" << endl; }
B(int i) {x=i;y=0; cout << "Constructor2" << endl; }
B(int i, int j) {x=i;y=j; cout << "Constructor3" << endl; }
~B(){ cout<<"Destructor" << endl; }
void print()
{ cout << "x=" << ",y=" << y << endl;}
}
void main()
{
B *ptr;
ptr = new B[3];
ptr[0] = B();
ptr[1] = B(5);
ptr[2] = B(2,3);
for(int i=0;i<3;i++)
ptr[i].print();
delete[] ptr;
}
//output:
Constructor1
Constructor1
Constructor1
Constructor1
Destructor
Constructor2
Destructor
Constructor3
Destructor
x=0,y=0
x=5,y=0
x=2,y=3
Destructor
Destructor
Destructor
Chapter-3 引用
引用的概念
引用不是变量,引用必须初始化
引用不是值,不占存储空间,引用只有申明,没有定义
引用只在声明时带有
&
,以后就像普通变量一样使用,不能再带&
指针变量也可引用
void main()
{
int n=10, *pn=&n, *&rn=pn;
(*pn)++; //n=11
(*rn)++; //n=12
}
对
void
的引用是不允许的不能建立引用的数组
没有引用的引用
没有空引用
引用作为函数参数
class Sample{...};
void fun(Sample s1,Sample &s2)
{
s1.setxy(12,18); //不能对目标对象操作
s2.setxy(23,15); //能对目标对象操作
}
常引用
常引用往往用作函数的形参,这样该函数中并不能更新该参数所引用的对象,从而保护实参不被修改
int x = 2;
const int &n = x;
n++; //错误,不能通过常引用更新对象,但执行x++是正确的
引用和指针的区别
两者不同点:
- 指针是个实体,而引用仅是个别名;引用不是变量,引用必须初始化,指针是变量,可以不用初始化。
- 指针是变量,可以不初始化。
- 引用不是值,不占存储空间,而指针变量会占用存储空间
Chapter-4 友元函数
友元函数概念
- 友元函数是一种能够访问类中的私有成员的非成员函数,提高了程序的运行效率,破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员
- 友元函数可以是多个类的友元
friend double dist(Line l, Point p);
- 友元关系不能被继承
- 友元关系是单向的,不具有交换性。
若类B是类A的友元,类A不一定是类B的友元
友元类
class A
{
...
public:
friend class B;
...
}
当一个类作为另一个类的友元时,意味着这个类的所有成员函数(B)都是另一个类的友元函数(A),可以调用另一个类的私有变量(A)
Chapter-5 运算符重载
重载为类的成员函数 类名 operator 运算符(参数表)
class Point
{
int x,y;
public:
Point(){}
Point(int i, int j) {}
Point operator+(Point &p){ return Point(x+p.x,y+p.y); }
};
重载为类的友元函数
class Point
{
int x,y;
public:
Point(){}
Point(int i, int j) {}
friend Point operator+(Point &p1, Point &p2)
{ return Point(p1.x+p2.x,p1.y+p2.y); }
};
一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数(=、()、[]、-> 不能重载为类的友元函数
当需要重载运算符具有可交换性时,选择重载为友元函数
其他运算符重载
- 一元自加/自减运算符重载
class Number
{
int x;
public:
Number(){ x=0; }
Number(int i){ x=i; }
void disp(){ cout<< "x=" << x << endl;}
void operator++() {x++;} //前置运算符
void operator++(int) {x+=2;}//后置运算符
} void main()
{
Number obj(5);
obj.disp();
++obj;
obj.disp();
obj++;
obj.disp();
} //output:
x=5
x=6
x=8
- 算术赋值运算符重载
class Vector
{
int x,y;
public:
void operator+=(Vector D) { x+=D.x; y+=D.y; }
void operator-=(Vector D) { x-=D.x; y-=D.y; }
}
- 关系运算符重载
class Rect
{
int length,width;
public:
friend int operator>(Rect r1, Rect r2)
{ return r1.length*r1.width > r2.length*r2.width?1:0 }
}
Chapter-6 继承与派生
单继承派生类的构造函数调用顺序
1. 基类的构造函数
2. 子对象类的构造函数(如果有的话)
3. 派生类构造函数
当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。基类中有默认的构造函数或者根本没有定义构造函数时,派生类不必负责调用积累的构造函数
基类指针引用一个派生类的对象。这种引用方式是安全的,但这种方法只能引用基类成员
派生类指针引用基类的对象,这种引用方式错误
多继承派生类的构造函数调用顺序
1. 调用基类的构造函数,调用次序按照它们被继承时声明的次序(从左向右)
2. 子对象类的构造函数,调用次序按照它们在类中声明的次序(与参数表顺序无关)
3. 调用派生类构造函数
#include<iostream>
using namespace std;
class Base1
{
public:
Base1(int x)
{cout<<"基类1构造函数"<<"X1= "<<x<<endl;}
~Base1()
{cout<<"基类1析构函数"<<endl; }
};
class Base2
{
public:
Base2(int x)
{cout<<"基类2构造函数"<<"X2= "<<x<<endl;}
~Base2()
{cout<<"基类2析构函数"<<endl; }
};
class Base3
{
public:
Base3()
{cout<<"基类3构造函数"<<endl;}
~Base3()
{cout<<"基类3析构函数"<<endl; }
};
class A:public Base2,public Base3,public Base1
{
public:
A(int a,int b,int c,int d)
:Base1(a),Base2(b),m1(c),m3(),m2(d)
//此处如果基类构造函数没有参数,则可省略
//基类和子函数的陈列,且顺序随意
//or:Base1(a),Base2(b),m1(c),m2(d)
{
cout<<"派生类构造函数"<<endl;
}
~A()
{
cout<<"派生类析构函数"<<endl;
}
private:
Base1 m1;
Base2 m2;
Base3 m3;
};
int main()
{
A obj(1,2,3,4);
return 0;
}
//Output
基类2构造函数X2=2
基类3构造函数 //若基类无带参构造函数可以省略,因此参数表中无基类3
基类1构造函数X1=1
基类1构造函数X1=3 //我们需要按照他们在类中声明的顺序来分别构造基类1、基类2、基类3
基类2构造函数X2=4
基类3构造函数
派生类构造函数
派生类析构函数
基类3析构函数
基类2析构函数
基类1析构函数
基类1析构函数
基类3析构函数
基类2析构函数
虚基类
- 虚基类的构造函数在非虚基类之前调用
- 若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的顺序调用
- 若虚基类由非虚基类派生而来,则仍然遵循先调用基类构造函数,再调用派生类中构造函数的执行顺序
class base1
{
public:
base1() { cout << "class base1" << endl; }
};
class base2
{
public:
base2() {cout << "class base2" << endl; }
}
class level1:public base2, virtual public base1
{
public:
level1() {cout <<"class level1" << endl;}
}
class level2:public base2, virtual public base1
{
public:
level2() {cout <<"class level1" << endl;}
}
class toplevel:public level1, virtual public level2
{
public:
toplevel() {cout <<"class toplevel" << endl;}
}
void main() { toplevel obj; }
//output
class base1
class base2
class level2
class base2
class level1
class toplevel1
toplevel1类中,level2为虚基类,因此,尽管level1在level2之前说明,但还是level2的执行在先。
level2中,base1类为虚基类,先执行。同一个虚基类只需要初始化一次,所以level1时不需要再初始化base1
Chapter-6 继承与派生(错题)
- [例7.31]
class base
{
public:
void who() { cout<<"base class"<<endl; }
}
class derive1:public base
{
public:
void who() { cout<<"derive1 class"<<endl; }
}
class derive2:public base
{
public:
void who() { cout<<"derive2 class"<<endl; }
}
void main()
{
base obj1,*p;
derive1 obj2;
derivel obj3;
p = &obj1;
p->who();
p = &obj2;
p->who();
p = &obj3;
p->who();
obj2.who();
obj3.who();
}
//output:
base class
base class
base class
derive1 class
derive2 class
指针引起的普通成员函数的调用仅仅与指针类型有关,和此刻指针正指向的对象无关
Chapter-7 多态性和虚函数
静态联编&动态联编
- 静态联编:编译时就解决了程序中的操作调用与执行该代码间的关系
- 动态联编:只有在程序执行时才能确定将要调用的函数(虚函数支持下实现)
动态联编实现条件:
- 类之间为基类与派生类关系
- 要有虚函数
- 调用虚函数操作的是指向对象的指针或者对象引用,或者由成员函数调用虚函数
虚函数
- 一个函数被声明为虚函数,即使重新定义类时没有声明虚函数,那么它从这点之后的继承层次结构中都是虚函数(若派生类声明为虚函数,基类没有声明为虚函数,应该以基类为准)
- 派生类的虚函数与基类中对应的虚函数的参数不同时,派生类的虚函数将丢失虚特性,变为重载函数
纯虚函数 & 抽象类
纯虚函数:基类中不能对虚函数给出有意义的定义
抽象类:带有纯虚函数的类称为抽象类,唯一用途是为其他类提供合适的基类
class Class_Name
{
virtual 类型 函数名(参数名) = 0;
}
虚析构函数
class A
{
virtual ~A() { cout<<"调用A::~A()" << endl; };
}
class B:public A
{
virtual ~B() { cout<<"调用B::~B()" << endl; };
}
void main()
{
A *a = new B();
}
//Output
调用B::~B()
调用A::~A()
//如果没有虚析构函数Output
调用A::~A()
Chapter-8 异常处理
异常基础概念
catch处理程序的出现顺序很重要,在一个try块中,异常处理程序是按照它出现的顺序被检查的。只要找到一个匹配的异常类型,后面的异常处理都将被忽略
void f(int code)
{
try
{
if(code==0) throw code; //引发int类型的yi'chang
if(code==1) throw 'x'; //引发字符型异常
if(code==2) throw 12.345; //引发double类型异常
}
catch(int n)
{ cout << "捕获整数类型: "<< n << endl;}
catch(...) //可以捕获任何异常
{ cout << "默认捕获"<< endl;}
return;
}
void main()
{
f(0);
f(1);
f(2);
}
//Output
捕获整数类型: 0
默认捕获
默认捕获
《C++ 习题与解析》笔记的更多相关文章
- HTML+CSS笔记 CSS笔记集合
HTML+CSS笔记 表格,超链接,图片,表单 涉及内容:表格,超链接,图片,表单 HTML+CSS笔记 CSS入门 涉及内容:简介,优势,语法说明,代码注释,CSS样式位置,不同样式优先级,选择器, ...
- CSS笔记--选择器
CSS笔记--选择器 mate的使用 <meta charset="UTF-8"> <title>Document</title> <me ...
- HTML+CSS笔记 CSS中级 一些小技巧
水平居中 行内元素的水平居中 </a></li> <li><a href="#">2</a></li> &l ...
- HTML+CSS笔记 CSS中级 颜色&长度值
颜色值 在网页中的颜色设置是非常重要,有字体颜色(color).背景颜色(background-color).边框颜色(border)等,设置颜色的方法也有很多种: 1.英文命令颜色 语法: p{co ...
- HTML+CSS笔记 CSS中级 缩写入门
盒子模型代码简写 回忆盒模型时外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上右下左. 语法: margin:10px 15p ...
- HTML+CSS笔记 CSS进阶再续
CSS的布局模型 清楚了CSS 盒模型的基本概念. 盒模型类型, 我们就可以深入探讨网页布局的基本模型了.布局模型与盒模型一样都是 CSS 最基本. 最核心的概念. 但布局模型是建立在盒模型基础之上, ...
- HTML+CSS笔记 CSS进阶续集
元素分类 在CSS中,html中的标签元素大体被分为三种不同的类型:块状元素.内联元素(又叫行内元素)和内联块状元素. 常用的块状元素有: <div>.<p>.<h1&g ...
- HTML+CSS笔记 CSS进阶
文字排版 字体 我们可以使用css样式为网页中的文字设置字体.字号.颜色等样式属性. 语法: body{font-family:"宋体";} 这里注意不要设置不常用的字体,因为如果 ...
- HTML+CSS笔记 CSS入门续集
继承 CSS的某些样式是具有继承性的,那么什么是继承呢?继承是一种规则,它允许样式不仅应用于某个特定html标签元素,而且应用于其后代(标签). 语法: p{color:red;} <p> ...
- HTML+CSS笔记 CSS入门
简介: </span>年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的<span>脚本解释程序</span>,作为ABC语言的一种继承. & ...
随机推荐
- kubernetes学习之二进制部署1.16
服务器规划和系统初始化 一.服务器规划 10.255.20.205 Master01 kube-apiserver.kube-controller-manager.kube-scheduler.ETC ...
- day 45
目录 form表单(**************) 参数 action method select标签 下拉框 textarea标签 CSS 注释 css的语法结构 css的三种引入方式 css查找( ...
- SpringCloud学习第三章-springcloud 父项目创建
父项目 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns=" ...
- AIX运维常用命令
目前传统的磁盘管理仍有不足:如果下Unix系统中的存储容量需要扩展,文件系统就必须停止运行,然后通过重构分区的手段来进行分区和文件系统的扩容.一般采用的方法是先备份该文件系统并删除其所在的分区,然后重 ...
- itextpdf5生成document生成pdf的简单dome
package dbzx.pdf; import java.io.FileNotFoundException; import java.io.FileOutputStream; import org. ...
- Odoo search 搜索视图详解与搜索视图工作原理
转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826430.html 搜索视图 搜索视图的search标签本身没什么属性可以使用,只要是<searc ...
- Elasticsearch高版本安装head插件
安装Elasticsearch 1.安装Elasticsearch-6.5.4.tar.gz [merce@info5 ~]$ cd /appmerce/zrapp/ [merce@info5 zra ...
- Fork/Join框架与Java8 Stream API 之并行流的速度比较
Fork/Join 框架有特定的ExecutorService和线程池构成.ExecutorService可以运行任务,并且这个任务会被分解成较小的任务,它们从线程池中被fork(被不同的线程执行)出 ...
- 对NetBackup 问题进行故障排除的步骤
错误消息通常是指出哪里出现故障的手段.如果在界面上没有看到错误消息,但仍怀疑有问题,请检查报告和日志. NetBackup提供了各种报告和日志记录工具, 这些工具可提供错误消息,直接为您指出解决方案. ...
- func_get_args call_user_func_array
<?php //call_user_func_array.php function test($arg1,$arg2) { $t_args = func_get_args(); $t_resul ...