「C++ 基础」类 & 对象
类 & 对象
定义
概念 | 描述 |
---|---|
类成员函数 | 类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。 |
类访问修饰符 | 类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。 |
构造函数 & 析构函数 | 类的构造函数是一种特殊的函数,在创建一个新的对象时调用。类的析构函数也是一种特殊的函数,在删除所创建的对象时调用。 |
C++ 拷贝构造函数 | 拷贝构造函数,是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。 |
C++ 友元函数 | 友元函数可以访问类的 private 和 protected 成员。 |
C++ 内联函数 | 通过内联函数,编译器试图在调用函数的地方扩展函数体中的代码。 |
C++ 中的 this 指针 | 每个对象都有一个特殊的指针 this,它指向对象本身。 |
C++ 中指向类的指针 | 指向类的指针方式如同指向结构的指针。实际上,类可以看成是一个带有函数的结构。 |
C++ 类的静态成员 | 类的数据成员和函数成员都可以被声明为静态的。 |
与 struct 区别
- 成员的默认访问权限不同 :class 默认 private,struct 默认 public
- class 可以用在模板中代替 typename,struct 不能。
可以看到区别很小 ,c++保留 struct 更多是为了兼容 C。但是语义上一般倾向于把struct当作C时代的struct来用,即只有成员变量,没有逻辑(或只有极其简单的数据存入读取逻辑),用来把多个变量打包成一个类型,而不用struct来做面向对象编程意义上的class。
抽象类
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
类成员函数
成员函数可以定义在类定义内部,或者单独使用范围解析运算符::
来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。
1 | class Box |
也可以在类的外部使用范围解析运算符::
定义该函数,如下所示:
1 | class Box |
类访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。
每个标记区域在下一个标记区域开始之前都是有效的。成员和类的默认访问修饰符是 private。
公有成员
公有成员在类的外部可访问。
私有成员
私有成员变量或函数在类的外部不可访问,只有类和友元函数可以访问私有成员。
保护成员
保护成员变量或函数可被派生类中的任何成员函数访问。除此外和私有成员相同。
类构造函数 & 析构函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
1 | class Line |
使用初始化列表来初始化字段
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化 ,可以:
1 | C::C( double a, double b, double c): X(a), Y(b), Z(c) // 最好要按照变量在类声明的顺序一致 |
复制控制
除了成员函数、构造函数定义了类型的对象的操作,类还可以通过特殊的成员函数(复制构造函数、赋值操作符、析构函数)来控制复制、赋值或撤销该类型对象时会发生什么。
类的析构函数
类的析构函数会在每次删除所创建的对象时执行。当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象超出作用域时,才会执行析构函数。
一般而言,如果需要析构函数,那么也需要赋值操作符和复制构造函数。
析构函数的名称与类的名称相同,在前面加~
作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
1 | class Line |
拷贝构造函数
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
编译器会默认定义拷贝构造函数。如果类带有指针变量,或者有动态内存分配,则它必须有一个拷贝构造函数。
因为默认的拷贝构造函数实现的是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源。这样,如果对象的数据成员是指针,两个指针对象实际上指向的是同一块内存空间,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。
所以我们就必须定义一个深拷贝构造函数,为新的对象分配单独的内存资源。
拷贝构造函数的最常见形式如下:
1 | classname (const classname &obj) { // obj 是一个对象引用,该对象是用于初始化另一个对象的。 |
1 |
|
如何防止默认拷贝发生
声明一个私有的拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类的对象,编译器会报告错误,从而可以避免按值传递或返回对象。
当出现类的等号赋值时,会调用拷贝函数
友元函数
友元机制允许一个类将对其非公有成员的访问权授予指定函数或类。友元不是授予友元关系那个类的成员,所以它们不受其声明出现部分的访问控制影响。
友元可以是一个函数,该函数被称为友元函数;也可以是一个类,称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
例:
1 | class Box |
作用域
友元声明将已命名的类或非成员函数引入外围作用域中。此外友元函数可以在类内部定义,该函数的作用域扩展到包围该类定义的作用域。
内联函数
由于一般调用函数需要许多工作:保存寄存器,复制实参,跳转到入口地址,返回时恢复寄存器等等。
内联函数可以避免函数调用时的开销。在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。函数名前面放置关键字 inline就可定义为内联函数。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
一般而言,内联机制适用于优化比较小的、经常被调用的函数。绝大多数编译器不支持递归函数的内联。
在类定义中的定义的函数都是内联函数。
this 指针
每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
1 | class Box |
指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 **->
**,就像访问指向结构的指针一样。与所有的指针一样,必须在使用指针之前对指针进行初始化。
1 | int main(void) |
类的静态成员
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。
- 常量变量:必须通过构造函数参数列表进行初始化。
- 引用变量:必须通过构造函数参数列表进行初始化。
- 普通静态变量:要在类外通过”::”初始化。
- 静态整型常量:可以直接在定义的时候初始化。
- 静态非整型常量:不能直接在定义的时候初始化。要在类外通过”::”初始化。
1 | class Box |
静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 ::
就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。