类相关 [持续补充]

类的设计规范

  1. 数据一定放在private中
  2. 构造函数使用初始化列表
  3. 参数传递 和 返回值 尽量使用引用
  4. 成员函数需要加const的一定不能少
  5. 头文件中的防卫式宏定义

头文件防卫式声明

#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); (成员函数需要传入对象的指针)
      在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值