C++OO基础
C++ OO基础
成员初始化表
- 构造函数的补充
- 执行
- 先于构造函数体
- 按类数据成员申明次序
class A
{
int x;
const int y;
int &z;
public:
A(): y(1), z(x), x(0) {x = 100;}
// 先执行x = 0的赋值,因为我们先声明了数据成员x
}
//下面是一段问题代码,我们需要注意到给p初始化的时候size还没有初始化
class CString{
char *p;
int size;
public:
CString(int x): size(x), p(new char[size]) {}
}
在C++98里面只有只有static const可以在类的内部初始化,其它类型都不可以像java一样直接在类的内部初始化。
使用初始化表可以减轻编译器Compiler的负担。
在C++11之后可以在类的内部初始化,在底层改进之后两种方式的效率已经差不多了,而且可以省去构造函数重载的时候重复初始化同一个变量的麻烦。
析构函数
- 格式:
~<类名>()
- 功能:RAII:Resource Acquisition Is Initialization(资源获取即初始化)
- 调用情况
- 对象消亡时,系统自动调用
- C++离开作用域的时候回收
- 使用delete关键字的时候进行调用
- Private的析构函数:(强制自主控制对象存储分配)
- 回收对象的过程被接管,保证对象在堆上进行创建,但是不能使用delete,那么我们可以在内容提供一个destroy()方法来进行回收
- 写在栈或者全局区是不能通过编译的(自动调用,发现调不到)
- 强制在堆上进行创建,对很大的对象而言有好处——强制管理存储分配
- 适用于内存栈比较小的嵌入式系统
为什么不像java一样实现GC?
效率障碍
存在不能用GC的场合
RAII: Resource Acquisition Is Initialization
释放对象持有的非内存资源
class A{
public:
A();
void destroy(){delete this;}
private:
~A();
};
//析构函数私有,无法声明
A a;
int main(){
A aa;//析构函数私有,无法声明
};
A *p = new A;//在堆上声明
delete p;//错误
p->destroy();//可能出现p的null空指针问题
//Better Solution
static void free(A *p){ delete p; }
A::free(p);
这边对象的数组不会因为类的消亡而被释放,因此我们需要自己实现一个析构函数。
拷贝构造函数
Copy Constructor
- 创建对象时,用一同类的对象对其初始化
- 自动调用
A a;
A b = a;
A f(){
A a;
...
return a;
}
f();
f(A a){
...
}
A b;;
f(b);
因此我们要求拷贝的时候
public:
A(const A& a);
需要传一个引用。
默认拷贝构造函数
- 逐个成员初始化(member-wise initialization)
- 对于对象成员,该定义是递归的
何时需要copy constructor?
防止出现悬挂指针的情况。
因此当我们需要深拷贝的时候需要自己实现拷贝构造函数。
//实例
class A {
int x, y;
public:
A() { x = y = 0; }
void inc() { x++; y++; }
};
class B {
int z;
A a;//已经默认创建了
public:
B(){ z = 0; }
B(const B& b):{ z = b.z; }
void inc() { z++; a.inc(); }//拷贝构造函数
};
int main() {
B b1; //b1.z = b1.a.x = b1.a.y =0
b1.inc();//b1.a.x = b1.a.y = b1.z=1
B b2(b1);//b2.z=1 b2.a.x=0 b2.a.y=0,这个时候调用的是A的默认构造函数
}
拷贝构造函数的两条规则:
包含成员对象的类
- 默认拷贝构造函数:调用成员对象的拷贝构造函数
- 自定义拷贝构造函数:调用成员对象的默认构造函数:程序员如果接管这件事情,则编译器不再负责任何默认参数。
可以参考这篇博客http://t.csdn.cn/Q6cie
移动构造函数
string generate(){
...
return string("test");
}
string S = generate();
//这个程序先创建了一个对象,再调用了一次拷贝构造函数,因此效率是很低的
//这里就需要我们使用移动构造函数了
右值引用A&&
先谈谈左值引用,可以把一个非常数的变量绑定到等式左边的一个变量上面,如上面的int &y = x, const int &z = x;
而对于上面的generate函数返回的右值,右值引用则可以支持绑定到右值上面,而不会绑定到左值上面。对于string或者vector这种ADT,拷贝构造函数的代价是很大的,因此我们就需要想办法避免用拷贝构造函数的方法构造。
而如图中的移动构造函数,我们在移动的时候最后将s.p置为空,避免了悬挂指针的问题。
所以像之前的string S = generate()
就可以改为string &&S = generate()
。这样更efficient,降低了拷贝的代价。
五三原则:拷贝构造、拷贝赋值、析构函数、移动构造、移动复制
- 在你需要对上面的五个函数中的一个进行自定义的时候,那么其他的四个编译器也不会进行默认方法的调用,都需要自己实现自定义。
- 在考试和机考中或许我们不会需要自己实现(也不做要求),但是作为一门高级程序语言,我们需要掌握这些技巧。
动态对象
- 在heap中创建
- new/delete
不用malloc和free,因为new可以调用对象的构造函数,而malloc是不行的。
为什么要引入new、delete操作符
调用constructor和deconstructor
此外,new是可以重载的,而malloc不可以。因为我们可以写带不同参数的构造函数。
栈上的对象都是有名对象,可以通过名称去访问,而堆上的都是无名对象,只能通过指针去访问。也就是说所有new出来的都是无名对象,只能通过指针去访问。
primitive是基本数据类型的意思
对象删除
- delete
- called on the pointer to an object
- Works with primitives & class-types
- Syntax
- delete ptrName
- Example
delete intPtr;
intPtr = NULL;
delete carPtr;
carPtr = NULL;
delete custPtr;
custPtr = NULL;
//我们需要将指针置为nullptr,以防止后面发生段错误,访问了不该访问的东西以致于自己都不知道哪边错了。
动态对象数组
- 动态对象数组的创建与撤销
A *p;
p = new A[100];
delete []p;
- 注意
- 不能显式初始化,相应的类必须有默认构造函数(可以忽略掉,新版本的c++是可以的)
- delete中的[]不能省(会少调用析构函数,并且会产生内存泄漏、段错误)
不过对于下面的情况是可以不需要[]的
int *p;
p = new int[100];
delete p;//对于内置的数据类型是不用调用析构函数的。但为了养成良好的习惯,我们要求new []和delete []是成对出现。
解释:
对于自己定义的数据类型,我们需要在前面空出四个字节去存储数组的长度,用来调用析构函数。但是对于内置的数据类型,我们不需要调用析构函数,因此在int [100]时我们是不会留出四个字节的,这样子我们delete p的时候直接释放的就是100个int的空间。而自定义类型,因为没有长度,所以会少调用析构函数,实际上只delete了第一个对象。
现在的C++版本支持显示初始化:
class A{
public:
A(int i){...}
}
int main(){
A *p = new A[5]{0,1,2,3,4,};//类比new int[5] {0,1,2,3,4}
}
动态2D数组
实际上多维数组更常用的方法是用一维数组模拟,操作时进行一波换算即可。
Const 成员
- const成员变量
class A {
const int x;
}
- 初始化放在构造函数的成员初始化表中进行
class A {
const int x;
public:
A(int c): x(c) {}
}
static const的值必须在类中定义的地方就初始化,因为static是所有对象共享的,所以不能由某个对象创建的时候初始化,因此我们需要在定义的时候就初始化。
当我们声明了一个const的对象,编译器如何判断哪些函数能够调用?
- const成员函数
- 对于没有声明成const的函数,调用它是不允许的
如果有人尝试在f函数后面加一个const以欺骗compiler?
编译器会报错,因为函数本身就修改了成员变量,不能标识为const。
正常来讲编译结果是这样的。show的标红的const表示A*的内容不能改变,const this则表示这个对象不能改变。const是函数重载的参数。
下面这个例子的f是可以标识为const的,因为indirect_int是一个引用,指向了堆上面的内存,属于外部变量,引用本身是不能修改的,我们修改的是指向的内容。
class A{
int a;
int & indirect_int;
public:
A():indirect_int(*new int){ ... }
~A() {
delete &indirect_int;
}
void f() const{
//只要不是直接修改变量的值就OK
//引用本身是不能修改的,所以编译器认为没问题
indirect_int++;//只是指向的内容发生了变化
}
};
//用a来做初始化
- 关键词mutable:表示成员可以再const中进行修改,而不是用间接的方式来做。
- 去掉const转换:
(const_cast)<A*>(this)->x
转换后可以修改原来的成员
静态成员
静态成员变量
- 类刻划了一组具有相同属性的对象
- 对象是类的实例
- 静态成员被类对象所共享,唯一拷贝,通过类访问控制
问题:同一个类的不同对象如何共享变量?
- 如果把这些共享变量定义为全局变量,则缺乏数据保护
- 名污染
- 为了保证只能被定义一次,因此不能写在.h中,要写在.cpp中(参考博客http://t.csdn.cn/3yJqm)
静态成员函数
可以在没有对象的情况下使用
只能存取静态成员变量,调用静态成员函数
遵循类访问控制
静态成员的使用
- 通过对象使用/通过类使用
- C++支持观点“类也是对象”
Resource control懒初始化原理
原则:谁创建,谁归还
将构造函数、拷贝构造函数、移动构造函数设为private,则只能在类内new
封装了对象的创建过程,使得由创建者负责整个初始化过程,而使用者只能调用static方法来调用。
友元
类外部不能访问该类的private成员
- 通过该类的public方法
- 会降低对private成员的访问效率,缺乏灵活性
- 例如:矩阵类(Matrix)、向量类(Vector)和全局函数(multiply),全局函数实现矩阵和向量相乘
分类:
- 友元函数
- 友元类
- 友元类成员函数
作用:
- 提高程序设计灵活性
- 数据保护和对数据的存取效率之间的一个折中方案
一些问题:
没有 classC,可以声明友元吗?不可以,因为需要知道内存空间【先声明后使用】
没有 classB,可以声明友元吗?可以
第一种情况:friend class B:
- 编译器会寻找有没有类 B
- 如果没有则会引入一个 B
第二种情况:friend B:
省略关键字的时候不会引入 B,如果没有 B 会报错模板类
但是这种形式常用于模板类 (T 或者 typedef 的时候来写)
一个错误的事例,因为Matrix调用Vector的时候还没有声明Vector,所以无法调用,但是声明可以是不完全的,所以我们可以先不完全声明一下Vector。
两个类相互引用:
- 为了隔离数据,权限控制
- 为了同时运行,派生出不同的变化
class B;
class A{
int a;
public:
void show(B &b);
friend class B;
}
class B{
int b;
public:
void show(A &a);
friend class A;
}
void A::show(B &b){
std::cout << b.b;
}
需要尽量满足迪米特法则,努力让接口完满且最小化。
迪米特法则LoD
- Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
- Each unit should only talk to its friends; don't talk to strangers.
- Only talk to your immediate friends.
迪米特法则 (Law of Demeter) 又叫做最少知识原则,也就是说,一个对象应当对其他对象尽可能少的了解。不和陌生人说话。英文简写为: LoD。
迪米特法则的目的在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。
迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系 —— 这在一定程度上增加了系统的复杂度。
友元的具体写法可以看这篇博客(55条消息) C++:友元(看这一篇就够了)_孙 悟 空的博客-CSDN博客_友元
- 标题: C++OO基础
- 作者: Kiyotaka Wang
- 创建于 : 2022-11-06 22:56:15
- 更新于 : 2024-01-15 12:50:09
- 链接: https://hmwang2002.github.io/2022/11/06/c-oo-ji-chu/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。