c++笔记

1,c++11之前,类的3个大原则,构造,拷贝,析构

2,c++11之前,左值和右值:能取地址的是右值,不能取地址的是左值,例如  &a可以,a是右值;&(2+3)不可以,2+3是左值。

 

 

3,c++11之后,左值和右值,演化出右值引用。

c++98可以这样:int a = 1;int& e = a;不可以这样:int& d = 1;

c++11引入了右值引用,可以直接利用右值,省去了中间变量a,可以这样:int&& d = 1;

 

下面说下右值引用的由来:

上图相当于调用了拷贝构造函数,右值拷贝到左值,也可以写成如下

上图就是调用了拷贝构造函数,将右值拷贝到左值,但是无论如何拷贝,这个拷贝过程却无形中多了一个临时变量,这个临时变量占用了内存,消耗了时间,为了省去中间变量,c++11之前的做法是如下

上图实现了省去左值,中间的变量,但是写的多,c++11对其进行改进,增加了右值引用

转变为:

auto v其实就是 std::vector<int> && v

到此,就是右值引用的由来

4,因为c++11之后多了右值引用,所以c++11,对应类的3大原则变为5大原则,多了右值相关的问题。

5,练习,关于左值和右值

auto& c=b;和std::vector<int>& c=b;是一样的

auto d=std::move(b);//move函数将b这个左值转换成右值,正常来说左值包含了右值一切的信息,所以左值可以转成右值,而右值不能转成左值。

6,const  &(const引用)

也就是说,const引用作为形参,可以传入左值,也可以传入右值,也可以传入一个引用变量;而不加const,不能传入右值

7,类的5大原则;

 

以下几点注意一下:

1)RuleOfFive e=b;//调用了正常的第一个拷贝构造函数

2)RuleOfFive d=std::move(b);//调用了第二个重载的拷贝构造函数

3)rhs.m_value=nullptr;//这句话没问题,当这个右值调用析构函数,delete m_value;也就是调用 delete nullptr;c++底层实现是对delete做了空的判断,如下:

4)上面的RuleOfFive类加入这个方法

//这个函数,加const的解释是:定义的类的成员函数中,常常有一些成员函数不改变类的数据成员,也就是说,这些函数是"只读"函数,而有一些函数要修改类数据成员的值。如果把不改变数据成员的函数都加上const关键字进行标识,显然,可提高程序的可读性。其实,它还能提高程序的可靠性,已定义成const的成员函数,一旦企图修改数据成员的值,则编译器按错误处理。 const成员函数和const对象 实际上,const成员函数还有另外一项作用,即常量对象相关。对于内置的数据类型,我们可以定义它们的常量,用户自定义的类也一样,可以定义它们的常量对象。

写法二:很多人喜欢如下写法,这样不仅浪费性能,而且m_value本来就是程序来控制的,既然要使用,就应该知道什么时候是空,不该使用,什么时候是可以使用,良好的习惯,建议不要这么写

写法三:(正确),如果错误,则终止程序,终止不是目的,目的是保证不在不该调用的地方调用

5)RuleOfFive d=std::move(b);之后执行下面的语句

b.print();//调用了右值引用构造函数之后,在调用右值的print方法,这个语法没问题,因为b虽然是右值,但是也是类,有左值类一切的成员,所以调用函数是正确的,不过函数内部出了问题,打印指针里面的内容*m_value这句,因为右值引用构造函数已经将这个指针置空了,所以这里会报错

6)c++建议调用了右值拷贝构造函数后,,被传入的右值尽管是完整了类或者其他类型,也不要继续使用那个右值了。因为在右值拷贝构造函数内部,使用者也许并不能看到内部做了什么,也许已经将资源取光了。

7)如果没有重载第二个拷贝构造函数,b.print();执行这句话是合法的

8)将=号重载,{}里面的实现是不对的,在这里,只是证明,=可以重载,具体哪里不对:

改成下面的,因为m_value的有可能有值,所以先delete,某则会内存泄漏,但是即使加了这句,还是不对,具体哪里不对:

为什么上面的不对,看执行下面的语句会出现什么情况:所以是不对的,那要怎么改?

最终正确版本:多了判断,抛开效率来说,这就是正确版本了,当然以正确为主

9)所以c++11之后类的5大原则就是:构造函数,析构函数,const引用拷贝构造函数和对应的=号重载const引用拷贝构造函数,右值引用拷贝构造函数和对应的=号重载右值引用拷贝构造函数。这

8,析构函数中不要抛出异常

1)析构函数定义,如下,如果注释掉,不写,也可以

如果类没有写析构函数,那么编译器就会自动给A和B生成析构函数,但是自动生成的析构函数是inline的,如下图

因为上面是编译器自动生成的,所以实际上面的代码还会被编译器优化,最后变成如下

2) B类 析构函数的执行过程

先执行  ~B(){}  --->执行B成员的析构函数 ~string(){} --->调用基类的析构函数~A(){}--->执行A成员的析构函数 ~string(){}

3)因为是inline,所以调用B析构函数的位置,都会被展开,如果有很多个调用点,都展开,那么这个程序会变得很大。展开是什么意思呢,就是上面说的析构函数的执行过程,全部拷贝。

4)在析构函数里面抛出异常,看看结果

按照下面的程序的意图,应该可以捕获到异常,

上图执行结果:

上图看到程序崩溃了,为什么

原因:c++的析构函数默认是noexcept,如下图,写不写,默认都加了noexcept。已经告诉编译器不抛出异常,但是仍抛出异常,那么编译器就会调用terminate函数,使程序崩溃掉。备注:C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理。如此逐级上传,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate

那么可以使上面的结果变成正确,捕获到异常吗?可以,如下图,告诉编译器,我要抛出异常

执行结果:

那么上面就没有问题了吗?有问题,给B的析构函数打印东西,看在派生类里的析构函数抛出异常,会出现什么问题

再执行程序,结果:发现B的析构函数会被调用,也就是说尽管抛出了异常,基类的析构函数仍然会调用,那么就没有问题了吗?其实执行过程是,EvilB在执行析构,虽然抛出了异常,但是在没有捕获之前,还会继续执行基类的析构函数,那是不是就没问题了?有问题

如果在B的析构函数也抛出异常,会发生什么?

执行结果:程序崩溃

分析:上面为什么会崩溃。c++一直强调的是,同一时间做一件事情,并且把这件事情做好。这里呢,EvilB抛出了异常,B也抛出了异常,同时抛出了两个异常,程序就处理不了,所以不要在析构函数里抛出异常。

那么,如果保证基类不抛出异常,只在最外层的类抛出异常,那么是不是就可以了呢?不可以。看下面,b和c变量的析构函数都会被调用

执行结果:b和c的基类的析构函数都被调用了,不过程序还是崩溃了,还是一个问题c++一直强调的是,同一时间做一件事情,并且把这件事情做好。c++承诺过,类的析构函数一定会被调用到,所以b和c的基类的析构函数都被调用了。而c++的try catch每次只能捕获一个异常。所以这里的问题是,抛出了两个异常。那么怎么解决呢。c++析构函数里不要抛出异常

还有一个不是主要的原因,看下面的例子,这样就会永远失去m_v这块内存,抛出异常后,不会删除内存。所以最主要的还是,c++析构函数里不要抛出异常

9,构造函数失败应该抛出异常

执行结果:前面4个左右执行输出很快,后面就会很快,说明物理内存用完了,就开始用虚拟内存,就开始往硬盘里写,所以开始慢了。备注:现代内存基本是无限的,物理内存用完,就会用虚拟内存,而虚拟内存就是硬盘,所以基本上内存用不完。

上面的例子,没有看出构造函数跑异常有什么好处,看下面的例子:

c的做法是:每次使用对象前先判断状态。

而c++的做法,如果构造出错,就抛出异常,如下:

而c++来说,try catch一般不用在函数里,而是用在main函数里,如下:备注(c++里不像java或者c#try  catch用的那么多,其实用的很少)

总结:

1c++关于错误信息,不能忽略,一定要在失败的地方,打log,并抛出异常,由上层catch到异常,然后在捕获处去处理对应的异常。

2stl标准库和c++的函数很少抛出异常,因为出于效率考虑,所以在使用时,要注意。

3swap函数不要抛出异常,也就是交换值的函数不要抛出异常

10,虚函数遇到构造析构:作用是,删除基类指针时,如果指向的是子类对象,会调用子类对象的析构函数

1)基类想调用子类的非virtual函数方法:将基类转成子类,这样下面三种转换都是对的

2)如果将基类转成本来就不是的类型,下面3中方法,只有dinamic_cast可以,成功返回正确指针,失败返回0;其他两个都会强转,调用才会出错

3)结构体继承,默认是公开继承,不用写public。c++子类的override是默认的,不需要显示加上。

4)结构体继承,基类构造函数调用虚方法,也就是构造函数执行的是自己本身的虚函数,并不会执行子类的覆盖后的虚函数,虚函数的特性不会体现。

原因是,c++构造函数的调用顺序是,先构造基类的构造函数,再调用子类的构造函数,(如果这里调用了子类的虚方法,就会直接报错的);c++析构函数的调用顺序正好是反过来的,先调用子类的析构函数,再调用基类的析构函数。

执行结果:

5)如果像上面的想法那样,类在构造的时候,基类想调用子类的虚方法,怎么做呢?c++一般做法如下:工厂类,但是这种方式不太好,现代c++编程代码最好不要显示的new 对象

6)析构函数前面什么时候加virtual,什么时候不加呢?c++virtual和override不同,virtual不是默认加的

potected时不需要加virtual:但是这时候testBase下的new Base();会报错。delete b;也会报错,因为析构访问权限是外部无法访问

下面就可以不报错:这就实现了基类析构函数不加virtual,虽然省去了一些额外的工作,但是放弃了用基类指针访问子类的特性。备注:加了virtual,c++做了很多额外的工作。c++protect用的很少,基本是private和public

11,auto 关键字

1)age有重名会报错。160.0f就是float类型,没写f就是double类型

执行结果:

2)

执行结果: p代表指针

 3)
for(auto v:group)   默认v的类型是值,改变v的值,不会影响group,v是局部的

for(auto& v :group)  v的类型是引用类型,改变v的值,会影响group

for(const auto& v :group)  v的类型是常引用类型,不能改变v的值,不会影响group

如下图,一个类,里面如果定义第一个public后的1,3,4条,或者2,3,4条,就可以用for循环遍历这个类。如下第二张图

12,左值引用和右值引用

 下图c++11新增类的创建方法,为什么,为了统一创建集合的规则;下下图,c++11之前是不可以的

下图可以

下图不允许

 c++98:

1引用就是变量的别名,可以正常改变这个变量的值,+const表示这个引用能读这个变量的值,但不能改变这个变量的值

2引用只能传递左值,+const的引用既能传递左值也能传递右值

c++11:

下图左值转换成右值

13构造和析构,智能指针的前因

1)

void* 在c/c++中,意思为指向任意类型,可以强转成其他类型。

下图c语言中malloc返回的是void*类型

下图c++中,为什么c++不用malloc,因为c++是强类型的,malloc返回的是void*类型,只是创建内存区,并不知道存什么类型

问题:不管c的malloc还是c++的new  都需要手动释放内存,free和delete p(delete [] ptr)

2)究竟需不需要手动释放资源?

下图,不需要手动释放返回的资源

下图需要手动释放资源

c语言的解决办法:

c++异常改变了c++程序处理流程

在badThing();处抛出了异常,但是没有捕获,那么程序就会崩溃,而下面呢资源没有释放掉,这时候就会造成内存溢出

14智能指针

智能指针在下图的库中

c++98   :下图,已经不推荐用了

c++11:

最常用的,下图

下图 只能有一个使用者

下图和  shared_ptr搭配使用

 1)接口常规使用

智能指针作为参数,下图

使用智能指针的好处,忘记删除和异常出现。

2)stdshared_ptr 用weak_ptr打破循环引用

下图,(备注:析构调用的顺序和构造是反过来的。c先调析构,p后调用析构)

执行结果:因为parent和child互相引用了各自的智能指针,都要等互相释放资源,才会彻底释放资源,那么就会出现智能指针的内存泄露问题,怎么解决呢?这就需要引入weak_ptr指针

 更改后

 

结果:

3)enable_shared_from_this

使用enable_shared_from_this就不推荐使用下面的用法,因为已经继承enable_shared_from_this,说明使用智能指针管理

 4)unique_ptr: 使用智能指针默认的选择,除非要共享

15 使用智能指针注意的几点:

1)

2)

3)

4)

5)裸指针4个字节(32位系统)8个字节(64位系统);智能指针需要存裸指针,存引用计数,存weak_ptr等(最起码3倍裸指针)

make_share代替new

6)

7)

8)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值