番禺制作网站报价,克拉玛依商城网站建设平台,网站管理页面,石家庄新闻广播✍个人博客#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 #x1f4da;专栏地址#xff1a;C/C知识点 #x1f4e3;专栏定位#xff1a;整理一下 C 相关的知识点#xff0c;供大家学习参考~ ❤️如果有收获的话#xff0c;欢迎点赞#x1f44d;… ✍个人博客https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 专栏地址C/C知识点 专栏定位整理一下 C 相关的知识点供大家学习参考~ ❤️如果有收获的话欢迎点赞收藏您的支持就是我创作的最大动力 唠叨唠叨在这个专栏里我会整理一些琐碎的 C 知识点方便大家作为字典查询~ 二、构造/析构/赋值运算
条款05了解 C 默默编写并调用了哪些函数
在创建类时如果自己不定义默认构造拷贝构造拷贝运算符析构函数那么编译器会自动生成这些函数。
//拷贝运算符
classname operator(const classname cn){......}但是有些情况下编译器不会自动生成拿下面这段代码举例 可以发现由于 class 里出现了引用类型和 const 类型故编译器就不会自动生成拷贝复制和移动赋值函数。
这是因为引用类型是引用其他地方的内容如果调用拷贝赋值和移动赋值可能会顺带改变引用处的值。
而 const 类型本身就是不可改动的所以不会生成拷贝赋值和移动赋值函数。
另外 mutex 本身不能被拷贝和移动所以拷贝构造和移动构造函数也不会自动生成。
条款06若不想使用编译器自动生成的函数就应该明确拒绝
对于类中拷贝构造函数我们应当阻止他们。但若是不声明编译器也会自动生成拷贝构造函数。
在现代 c 中我们可以通过 delete 关键字对编译器自动生成的函数进行删除如下图所示 但在之前的 C 中并没有该关键字所以可以用私有化的方式进行。
class person
{
private:person(const person);person operator(const person);//参数是不必要写的毕竟这个函数不会被实现
public:......
};编译器自动生成的函数都是 public 函数所以我们将 public 改为 private就可以防止对象调用拷贝构造。
注private 只有成员函数和友元函数可以调用。
同时也产生了一个问题如何防止拷贝在成员函数或友元函数中被调用
答案是建立一个父类在父类中定义 private 拷贝函数子类( person 等等)继承父类。因为子类不可以调用父类的 private 函数
class uncopyable
{
private:uncopyable(const uncopyable);uncopyable operator(const uncopyable);
};class person{......};条款07为多态基类声明virtual析构函数
多态把父类当作一个接口用以处理子类对象利用父类指针指向一个在堆区开辟的子类对象。
class person
{
public:person();......~person();
};class teacher: public person{......};person* p new teacher(...);
...
delete p;
//在堆区开辟的数据要手动删除上述代码是有问题的。
我们知道在普通类继承里删除子类对象会先调用子类的析构再调用父类的析构。但在多态里情况有所不同。我们删除的是父类指针调用的只是父类的析构函数子类析构不会被调用也就是说子类对象没有被删除而指针却没了。这是局部销毁会造成资源泄漏等错误。
幸运的是我们可以通过虚函数来解决这个问题。
在多态里虚函数可以让子类重写父类的函数同时在虚函数表中生成一个指针找到子类重写函数的地址从而让我们可以通过父类访问子类重写的函数。
class person
{
public:person();......virtual ~person();
};class teacher: public person{......};person* p new teacher(...);
...
delete p;
//删除 p 的时候调用 virtual ~person();
//virtual 找到子类析构函数的地址导致子类也可以被删除纯虚函数使得父类更像一个接口这里不用多说。
注多态里父类应该至少有一个虚函数(virtual 析构)若不用做多态则类里不应该有虚函数。
条款08别让异常逃离析构函数
释义在析构函数内部处理异常
我们来看以下案例 如果在 db.close() 处发生异常则会导致不可预料的情况。
首先介绍一下异常处理的办法
try
{...}
//try 内部写可能产生异常的语句没有产生异常则catch语句不执行产生则一一匹配
//catch 用于捕获并处理异常和 case 有异曲同工之妙
catch(...)
{1、可以使用 abort(); 函数终止程序2、可以吞下这个异常在 catch 内部做一些处理
}了解如何处理异常之后我们就可以实现如条款所说在析构函数内部处理异常 上面左边的方法是直接调用 abort 终止程序右边则是直接吞下异常只是记录个日志后面再处理。
但是这两种方法都有个缺点就是用户无法参与操作因此可以写成下面的方式 用户可以自己实现一个 close 函数来进行关闭如果关闭的顺利则 closedtrue反之关闭失败则会进行异常捕捉在析构函数中帮助用户关闭。
条款09绝不在构造和析构函数中使用虚函数
众所周知在类的操作中父类比子类先构造而子类也比父类先析构多态也是如此多态先通过 virtual 找到子类析构再析构父类所以在构造父类的时候子类对象还未进行初始化在析构父类的时候子类已经被销毁。来看下面这个例子 此时如果父类的构造和析构函数中有 virtual则该函数无法找到子类的地址或者说无视子类因为子类被销毁/未被初始化使程序发生不明确的行为。
可以发现上面我是想调用派生类的构造函数和析构函数但是调用的却是基类的。如果想满足该要求我们可以去掉虚函数而是在基类接收一个参数来实现。 条款10令 operator 返回一个 reference to *this
释义让赋值运算符重载版本返回一个自身以便实现链式编程。
class employee{
public:int m_salary;employee(int a)//有参构造赋工资初值{this-m_salary a;}employee operator(const employee ep){this-m_salary ep.m_salary;return *this;}//返回其本身
};employee e1(5000);employee e2(50000);employee e3(123456);e1 e2 e3;//链式编程条款11在 operator 中处理自我赋值
我们来看一段代码
class person{...};
person p;
p p;这是自我赋值这种操作看起来有点愚蠢但是并不很难发生。
比如一个对象可以有很多种别名客户不经意间让这些别名相等
或者如之前所说父类的指针/引用指向子类的对象也会造成一些自我赋值的问题。
自我赋值往往没有什么意义还会有不安全性。
class student{...};
class teacher
{...
private:student* s;
};teacher teacher::operator(const teacher teach)
{if(s ! NULL){delete s;s NULL;}s new student(*teach.s);return *this;//便于链式操作
}上述代码是不安全的。如果 *this 和 teach 是同一个对象那么客户在删除 *this 的时候也把 teach 删除了s 就会指向一个被删除的对象这是不允许的。
我们提供三种方法以解决这个问题
1、证同检测
teacher teacher::operator(const teacher teach)
{if (this teach)return *this;//证同检测if (s ! NULL){delete s;s NULL;}s new student(*teach.s);return *this;//便于链式操作
}遗憾的是证同检测可以保证自我赋值的安全性但是不能保证“异常安全性”。即如果 new student 抛出异常则 s 就会指向一个被删除的对象这是一个有害指针我们无法删除甚至无法安全读取它。
2、记住原指针
teacher teacher::operator(const teacher teach)
{student* stu s; //记住原指针if(s ! NULL){delete s;s NULL;}s new student(*teach.s); //如果抛出异常s 也可以找回原来地址delete stu; //删除指针return *this;//便于链式操作
}3、copy and swap:
void swap(const teacher teach)
{......}teacher teacher::operator(const teacher teach)
{teacher temp(teach); //拷贝一个副本swap(temp); //将副本和 *this 交换 return *this;//便于链式操作
}交换操作不要考虑原本指针内容可以保证赋值安全性同时也能保证异常安全性。
条款12复制对象时勿忘其每一个成分
释义自定义拷贝函数时要把类变量写全子类拷贝不要遗漏父类的变量。
父类变量通常存储在 private 里子类不能访问父类 private 对象所以应该调用父类的构造函数。
class animal
{
public:animal(const animal an){......}animal opeartor(const animal an){......}
......
private:string typename;
};class cat: public animal
{
public:cat(const cat c);cat operator(const cat c);private:string cat_type;
};cat::cat(const cat c):cat_type(c.cat_type),//为了不遗漏父类变量调用父类函数animal(c)
{}cat::cat operator(const cat c)
{//为了不遗漏父类变量调用父类函数animal::operator(c);this-cat_type c.cat_type;return *this;
}值得注意的是上面代码 copy 函数和 “” 运算符调用的都是和本身一样的函数。究其原因copy 函数是创建一个新的对象operator 是对已经初始化的对象进行操作。
我们不能用 copy 调用 operator因为这相当于用构造函数初始化一个新对象父类尚未构造好。
同理也不能用 operator 调用 copy这相当于构造一个已经存在的对象父类已经存在了。 参考资料 《Effective C》侯捷https://book.douban.com/subject/1842426/ EFFECTIVE C 万字详解(一)_c effectivehttps://blog.csdn.net/qq_62674741/article/details/124896986 一文整理Effective C 55条款内容全https://blog.csdn.net/weixin_45926547/article/details/121276226?spm1001.2101.3001.6650.1utm_mediumdistribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-1-121276226-blog-124896986.pc_relevant_multi_platform_whitelistv3depth_1-utm_sourcedistribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-1-121276226-blog-124896986.pc_relevant_multi_platform_whitelistv3utm_relevant_index2