必须注意的知识点:
1. C++中定义常量时,应该尽量采用const变量定义的方式,而尽量不要采用宏定义的方式。
2. 类的析构函数应该定义成虚函数,除非能够确定这个类不会被其他类继承。
3. 应该尽量的避免从基类向子类的类型转换,因为向下的类型转化并不是安全的, C++中由子类向父类进行的类型转换是安全的。
4. 可以使用const来修饰成员函数表示成员函数中不会修改类的成员变量。
5. 可以使用const来修饰类的数据成员,表示这个类的所有对象的这个数据成员的值都相等(这个是不正确的)。
原因是const修饰类的数据成员仅仅在某个类的对象的生命周期内是常量,类没有实例化的时候常量是未知的。
参考14. 类中的常量.
6. 使用成员初始化列表对类的数据成员进行初始化的时候尽量要按照在类中声明的顺序进行初始化,不然在会由于顺序的问题出现错误。
7. 包含纯虚函数的类不能被实例化。此时的类是抽象类,不能用来初始化对象!!
8. 被声明成static的数据成员,不属于类的大小,sizeof(类对象)的值不包含static的数据的大小,另外没有数据成员的类的sizeof(类对象)的大小是1.
9. const常常用来修饰函数参数、常量对象等,以防止其被改变,所以const变量的值不可能被改变。(不明白为什么)
10. 局部静态变量只在第一次执行的时候进行初始化(只初始化一次),以后再次执行不再初始化,保留以前的值。
11. static用来修饰类的成员函数,可以不用实例化通过类名,直接调用该函数
12. 多态通常是借助于虚函数来实现的。
13. 重载是发生在编译期,编译之后会生出具有不同的参数类型的同名函数。但是模板complex<double>是在运行的时候才进行替换。模板类型参数作为函数的参数是不能用 来作为重载的。因为此时生成的具有相同的参数类型的同名函数。
在多个文件之间编译相同的函数模板定义增加了不必要的编译时间.
14. STL是C++标准的一部分
15.char str[10];str="string";(这是错误的)【其中str是该数组的数组名字,而且str还是该数组的首地址,也就是十个元素中的第一个元 素的地址,但务必注意str是一个指针常量,它是不能被赋值的也不能进行自增自减的!】
正确的是:char str;str="string";
16.
设有如下 class 定义:
class CA
{
public:
CA(void) { ++CA::ms_iCounter; }
~CA(void) { --CA::ms_iCounter; }
static int ms_iCounter;
}
int CA::ms_iCounter = 0; //初始化的时候不要带上static
则,
int mian()
{
CA a[2]; //两次调用构造函数
CA b = a[1];
cout << CA::ms_iCounter;
//这个位置还没有调用析构函数
return 0;
} //这个地方才会调用析构函数
结果是 2;
***析构函数的调用时机有两种:1.像例子一样这种没有动态分配的(没有new)的,析构函数的调用时机是碰到最后结尾的右括号之后,所以上一题没有调用析构就输出了cout << CA::ms_iCounter; 结果是2;2.就是动态分配的时候,只有在指向该对象的指针被delete的时候才调用析构函数,不然的话不会运行析构函数,对象一直存在,导致内存泄露!!
补充题目::
#include<iostream>
结果是:
(A) C B A D ~C此时的答案是A,因为在初始化的时候构造函数调用的顺序是:1.基类的构造函数,2.派生类中的数据成员中的类成员的构造函数。3.派生类自己的构造函数。
在析构的时候分两种情况。1.当不是new的时候(不是动态分配的)在程序结束(“}”)之后就开始隐含的调用析构函数。顺序和构造函数相反.
2.就像上一题是动态分配的,此时只有delete才会调用析构函数,不过此时要注意在delete类的指针时要看看指针所指的类的析构是不是virtual,不是虚函数就按照先调用自己的析构函数然后父类的析构,派生类的~D不管,其他成员数据类的析构~A也不管(不过如果此时delete的指针的众多父类中,如果有一个父类的析构函数是虚的,析构顺序就和构造顺序相反)。但是当指针所指的类的析构函数是virtual的时候就会先去调用自己的析构函数然后派生类的析构函数然后调用父类的析构。
(B) C B A D ~D~A~B ~C 此时 ~C(){cout<<"~C ";} 是virtual ~C(){cout<<"~C ";} 就会先去调用D的析构函数。所以在写程序的时候最好是把基类的析构函数定义成virtual的,除非保证他不会做基类,否则在动态new对象delete析构的时候容易导致内存泄露!!
int_tmain(intargc,_TCHAR*argv[])
{
D d(1,2,3);
return 0;
}结果还是(B) 此时的析构的调用时隐含进行的,按照构造函数相反的顺序进行调用析构函数!!
17.
设有如下 class 定义:
class CA
{
public:
virtual void fun(int i)
{ cout << "void CA::fun(int i)" << endl; }
};
class CB : public CA
{
public:
virtual void fun(int i)
{ cout << "void CB::fun(int i)" << endl; }
virtual void fun(double d)
{ cout << "void CB::fun(double d)" << endl; }
};
则,
CB b;
CA & a = b;// 用派生类对象初始化基类对象的引用
a.fun(5.0);
b.fun(5.0);
以上代码的执行结果是( C )
B) void CA::fun(int i)
void CB::fun(double d)
C) void CB::fun(int i)
void CB::fun(double d)
引用和多态
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
class A;
class B:public A{……};
B b;
A &Ref = b; // 用派生类对象初始化基类对象的引用
Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
18.
class Base
{
public:
virtual void f( int iParam){
cout << "Base::f(int)" << endl;
};
virtual void g( int iParam){
cout << "Base::g(int)" << endl;
};
};
class Derived: public Base
{
public:
void f( float fParam){
cout << "Derived::f(float)" << endl;
};
static void g( int iParam){
cout << "Derived::g(int)" << endl;
};
};
int main()
{
Base b;
Derived d;
Base* pb = new Derived;
d.f(10);
pb->f(1.0);
pb->g(10);
delete pb;
return 0;
}
结构是:
(D) Derived::f(float)//调用子类的对象
Base::f(int) //用virtual实现的多态是重写父类的方法,参数也不能改变,不然不能实现多态
Base::g(int) // staitc函数不能实现多态
19.如果类的数据成员定义为static,那么该数据成员可以被看成是属于该类的, 类对象拥有该数据成员的同一拷贝。
static成员函数,只能操作static数据成员
对上面的几点出错点的解释:
5.
class A {
A(int i):a(i){};//注意这里的初始化
~A(){};
private:
const int a;
}
const的数据成员必须用成员初始化器进行初始化,在上面的类中,我们可以看出每次在声明一个新的类对象的时候带参数的构造函数每次传的i值不同,这个类的对象的这个数据成员a的值就不相同。
6.
class array {
public:
array(int low, int high);
...
private:
size_tsize; //数组中元素的数量
int lbound;
int hbound; // 下限,上限
};
array::array(int low, int high) //high> lowb
: lbound(low), hbound(high), size(hbound -lbound + 1)
{ …… }
此时的size(hbound -lbound + 1)首先计算,但是hbound ,lbound 还没有赋值,所以会出错的!!
构造函数列表的初始化方式不是按照列表的的顺序,而是按照变量声明的顺序。
8.
class Base
运行结果是:8
其中虚指针是4,空的Base的sizeof(m_ob)是1,按照对齐原则是8;
class Base
{
public:
Base (){};
~ Base (){};
};
class CDemo
{
public:
CDemo() {}
~CDemo() {}
protected:
static int m_siObjCnt; //sizeof(类对象)此时static变量的大小不计算在内
Base m_ob;
};
void main()
{
CDemo ob;
cout << sizeof(ob) << endl;
}
运行结果是:1
为什么为什么空类占一个字节?
所谓类的实例化就是在内存中分配一块地址.(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.因为如果空类不隐含加一个字节的话,则空类无所谓实例化了(因为类的实例化就是在内存中分配一块地址。
继承这个类后这个类大小就优化为0了。这就是所谓的空白基类最优化 ;比如:
class Base
{
public:
Base (){}
~ Base (){};
};
class CDemo :public Base
{
public:
CDemo() {}
int a ;
protected:
static int m_siObjCnt; //sizeof(类对象)此时static变量的大小不计算在内
};
void main()
{
CDemo ob;
cout << sizeof(ob) << endl;
}
运行结果是:4
10.
int func1 (int x) {
static int sum = 0; //仅仅在第一次执行的时候执行初始化,以后不在执行初始化。
sum += x;
return sum;
}
int main (int argv, char **args)
{
printf (“%d, ”, func1 (10));
printf (“%d”, func1 (10));
return 0;
}
执行结果是: 10,20
13.
简单点说,对于一个vector<int>的函数,比如size(),如果在不同的cpp中出现,在这些文件编译的时候都要把vector<int>::size()编译一遍。然后在链接的时候把重复的函数去掉。很显然增加了编译时间。
模板的声明是不编译的,更没有空间,它根本不是实际的类型或函数,编译器根本不管它。只有实例化的时候,才生成真正的类型、函数,而实例化的地方不在一起,必然造成同一个类型、函数被编译了多次.
所谓实例化就是用具体的类型去替换模版类型参数,然后这个时候才像普通的函数那样去编译的。
class Base
{
public:
virtual void f( int iParam){
cout << "Base::f(int)"<< endl;
};
virtual void f( double dParam){
cout << "Base::f(double)"<< endl;
};
virtual void g( int i = 10 ){ //避免在虚函数形参中提供默认初始化值
cout << i << endl;
};
};
class Derived: public Base
{
public:
void f( complex<double> Param){ //这里的模板参数是不能实现重载的
cout <<"Derived::f(complex)" << endl;
};
void g( int i = 20 ){
cout << "Derived::g() "<< i << endl;
};
};
int main() {
Base b;
Derived d;
Base* pb = new Derived;
b.f(1.0);
d.f(1.0);
pb->f(1.0);
delete pb;
return 0;
}
运行结果:Base::f(double)
Derived::f(complex)
Base::f(double) //没有重载还是调用的父类的函数的方法
测试2:
int main() {
Base b;
Derived d;
Base* pb = new Derived;
b.g();
d.g();
pb->g();
delete pb;
return 0;
}
运行结果:
10
Derived::g() 20
Derived::g() 10 //默认实参是静态绑定的,此时的动态方式父类指针调用子类方法但是默认实参也要动态传下去到派生类中。
不幸的是,默认初始化值是静态绑定的,从基类类型出发以静态方式决定的默认参数初始化值是10,于是这个值就被传给了以动态方式绑定的派生类类型的虚函数调用中。14. 类中的常量
有时我们希望某些常量只在类中有效。由于#define 定义的宏常量是全局的,不能达
到目的,于是想当然地觉得应该用 const 修饰数据成员来实现。 const 数据成员的确是存
在的,但其含义却不是我们所期望的。 const 数据成员只在某个对象生存期内是常量,而
对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const 数据成员
的值可以不同。
不能在类声明中初始化 const 数据成员。以下用法是错误的,因为类的对象未被创
建时,编译器不知道 SIZE 的值是什么。
class A
{…
const int SIZE = 100; // 错误,企图在类声明中初始化 const 数据成员
int array[SIZE]; // 错误,未知的 SIZE
};
const 数据成员的初始化只能在类构造函数的初始化表中进行,例如
class A
{…
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的初始化表
{
…
}
A a(100); // 对象 a 的 SIZE 值为 100
A b(200); // 对象 b 的 SIZE 值为 200
怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中
的枚举常量来实现。例如
class A
{…
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
};
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:
它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159)。