文章目录
类的设计规范
- 数据一定放在private中
- 构造函数使用初始化列表
- 参数传递 和 返回值 尽量使用引用
- 成员函数需要加const的一定不能少
- 头文件中的防卫式宏定义
头文件防卫式声明
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
类的两种经典分类
- class without pointer members : complex
- class with pointer members : string
Big three
- 有指针的类必须自定义实现下列三个函数 (因为它们默认是浅拷贝)
- 拷贝构造函数 : 原来没有, 根据另外一个构造一个一模一样的
- 拷贝赋值 : 原来已经有了, 根据另外一个把已有的进行改造
- 析构函数 :
- 一个类默认会实现四个函数, 上述三个+无参构造函数
构造函数
构造函数的初始化列表 & 函数体赋值
无参构造函数 & 带有默认参数的构造函数
class Complex
{
public:
Complex (double r = 0, double i = 0) : re(r), im(i) { } // 默认参数必须放在最后
Complex () : re(0), im(0) { }
private:
double re, im;
};
void test () {
Complex c1; // ambiguous 有歧义, 编译器不知道调用哪一个
}
私有化构造函数 (Singleton)
成员函数
隐藏的this指针
- 不能在参数中写出来, 但可以在函数体中直接使用
- 上面的对象调用, 和下面的对象调用要区别开
- 上面的对象调用的是非虚函数OnFileOpen, 在这个函数里用指针调用了虚函数, 因此是dynamic binding
- 下面的对象调用的直接是虚函数, 不符合那三个条件.
类内实现 & 类外实现
- 类内实现相当于一个inline function
- 类外实现增加inline关键字
建议
编译器使用inline function
常量成员函数
- 常量成员函数, 告诉编译器这个函数里
不能对成员数据进行更改
(但是可以对非成员数据进行修改
) 常量对象
只能调用常量成员函数
- 常量对象, 告诉编译器这个对象中的成员数据是不能改变的
- 非常量成员函数, 告诉编译器这个函数里
可能涉及对成员数据的改变
- 两者矛盾, 因此不能调用
非常量对象
可以调用常量成员函数
, 也可以调用非常量成员函数
- 在设计类时, 需要考虑每个函数的功能意义, 添加合适的关键字.
class Complex{
public:
Complex(double r = 0, double i = 0) : re(r), im(i) { }
double real () const {return re;}
double imag () const {return im;}
private:
double re, im;
};
void test02() {
Complex c1(1,2);
cout<<c1.real()<<endl;
cout<<c1.imag()<<endl;
const Complex c2(2,3); // 如果成员函数不是常量的, 这里会出现报错
cout<<c2.real()<<endl;
cout<<c2.imag()<<endl;
}
成员函数的 const 和 non-const 版本同时存在时
const object 只能调用 const 版本
non-const object 只能调用 non-const版本
- const属于函数签名的一部分, 因此下面两个函数签名不一样, 可以同时存在
- 字符串的底层实现是 reference counting, 当其中一个reference更改数据时, 需要进行COW
- operator[] 操作符可能被使用者用来更改数据
- 常量字符串调用[], 不必考虑COW, 调用const版本, 效率更高.
- 非常量字符串调用[], 需要考虑COW, 调用non-const版本
友元
同一个类的所有实例之间互为友元
class Complex
{
public:
Complex(double r = 0, double i = 0) : re(r), im(i) { }
int func(const Complex& c) {return c.re + c.im;}
private:
double re, im;
};
void test() {
Complex c1(1, 2);
Complex c2;
cout<<c2.func(c1)<<endl;
}
static & non-static
- 静态成员函数的引入是为了访问私有的静态数据成员
- 非静态成员函数也可以访问静态数据成员, 但是需要一个额外的this指针, 并且这个this指针在访问过程中没有什么用, 因为静态数据成员在数据段而不在对象分配的内存中, 因此为了逻辑正确性, 也为了有一种独立于对象之外的对静态数据成员的访问, 引入了静态成员函数
- 引入静态成员函数之前, 对静态数据成员的访问 : ((T*)NULL)->Func(); (为了避免对象的实例化操作)
注意 : 类的封装是从逻辑层面考虑的, 而不是代码层面(那些函数需要调用哪些数据)
- 类实例化时, 每个对象都会有一份类里的non-static data members, 但是static data members , member unctions, static member functions 是所有对象共有的, 一个类只有一份, 类所占的内存空间不包括他们
- member functions都会有一个隐含参数, this指针. 函数体内也是通过this指针来调用不同对象的数据.
- static members 是独立于对象的, 一个类只有一份.
- static member function 没有this指针, 因此他无法访问实例对象的数据. 它只能处理static data members
- static data members 在类内声明后, 还需要在类外进行定义 (定义 : 分配内存), 可以不显示赋值就会默认分配0.
全局的静态变量定义时, 如果已存在就会使用已存在的, 如果不存在才会进行定义.
继承 和 虚函数
- 子类继承父类, 虚指针和虚表也会同样继承. 但是子类可以override父类的虚函数, 会把虚标中对应的虚函数地址换成自己override后的虚函数地址.
- 虚函数表类似一个数组, 编译器将虚函数按顺序放入其中,调用时, 也按照对应的索引调用
- 由于虚函数有可能被重写, 因此同一个索引对应的函数在运行时地址可能不同, 因此需要动态绑定.
- 动态绑定的三个条件 :
指针调用
,向上转型
,调用虚函数
- *(p->vptr)[n](p); (成员函数需要传入对象的指针)
- 动态绑定的三个条件 :