默认成员函数的控制
1 语法上:
如果用户没有显示定义构造,拷贝构造等默成员函数,编译器会自动生成一个默认的成员函数。
实际上: 并不是,有的编译器会,但编译器不会。
eg VS2013会看情况而定,如果真的需要再生成,不是真的需要就不生成。
2 拿构造函数来说:
1 组合 : 如果A类含有B类对象,那么会默认生成(因为B类对象初始化列表)
2 继承: 如果发生继承时,基类带有无參或全缺省的构造函数,那么编译器会默认生成派生类的构造函数
3 在虚拟继承时,在构造对象阶段将指向虚函数表的指针放在对象的前四个字节
4 如果类中含有虚函数,需要在构造对象阶段将指向虚函数表的指针放在对象的前四个字节。
智能指针中 :
C++和Boost的智能指针
unique_ptr 独一无二的ptr;
实现原理 :
**防拷贝,防赋值,**c++98私有化只声明,而c++11 delete 告诉编译器删除默认的成员函数
new
c++11 之前,不能用new来创建默认对象,
eg: int* p = new int 【10】{123456459};
c++11 就可以。
多态中
虚函数重写-------多态的重要条件之一
在多态中可能会在重写虚函数时,写反了一两个字母而不容易发现,C++提供override这个编辑器来严格检查,早发现问题,早修改问题
注意override只能修饰派生类中的虚函数,不能修饰基类中的虚函数。
final :
修饰类时,该类不能被继承
修饰函数::final修饰函数必须是虚函数,作用,禁止该虚函数在其子类中被重写
题外话:shared_ptr
shared_ptr 引用计数的方式来管理内存
1 方式 引用计数(指针,因为需要修改计数,引用计数是多个智能指针对象共享的)
2 引用计数的空间在堆上维护,因此new delete来管理
3 线程安全问题,解决方式,引用计数修改之前加上互斥锁。
shared_ptr循环引用问题类似于死锁
解决方式 :将shared_ptr换成weak_ptr若指针
weak_ptr 就是用来解决shared_ptr循环引用的问题
C++98 : auto_ptr :
C++11 shared_ptr &&weak_ptr
C++ 11 引入的序列化容器
forward_list :单链表
C++98不支持动态数组:
eg: int * a =new int[4 ]{2345}
但C++11可以
列表初始化(注意不是初始化列表)
class A{1,2 }
初始化列表是初始化一个对象,
列表初始化初始化多个对象
C++的类型推断有两种风格:
1 auto
2 decltype
主要用于推倒表达式的类型
运行时类型推断:
1 typeid( x ).name()
2 dynamic_cast() //强制类型转换的推演,成功则转换,失败,返回0
dynamic_cast() 你需检测含有虚函数的类中。
decltype:
可以作为检测表达式的返回值,
借助typeid().name() 可以显示返回值类型。
推演期间不会调用函数,因为推演发生在编译期间。
看下图你就明白了
引用 && 右值引用
c++98 引用
c++98既可以引用左值,也可以引用右值,需要引用左值可与➕const
引用变量和其引用的类型共用一段内存空间。
引用的底层实现原理,还是按照指针的方式实现。
引用就是对象的别名,他的出现弱化了指针。
在C++中需要用到一级指针可以用引用替代
二级指针就可以用一级指针的引用来替代,
处理二级指针复杂性比较高,那么一级指针的引用就可以简单的处理问题了。
例如二叉树的销毁,必须得销毁(每个)根节点,因此可以使用一级指针的引用。
C++11 右值引用
C++11 为了提高程序的运行效率,提出
右值引用。本质还是引用,只能引用右值
那
什么是右值?
eg T&& ref = 10;
什么是左值:
int a =10;
T&& b =a;
这样不行!!!
这样不行!!!
这样不行!!!
那什么是左值? 什么是右值?
C语言提出这个概念,但也比较模糊。
一般来说:
1 要么 只能放在 等号左边
2 要么 能够取地址的称为左值
1 const 类型变量(可以引用)
2 临时遍历
3 表达式的结果
函数如果按照引用的的方式返回,可以作为左值,因为返回的就是本身。
C++11 严格规定右值:
1 纯右值。 eg: a+b, 100 ,
2 将亡值,比如表达式的中间结果。又比如 函数返回的临时结果。
C++11 认为不符合右值的全是左值
我感觉C++11标准就看是不是存储在新开辟出来空间的,开辟空间了称为 右值。
C++11只能引用右值,不能直接引用左值,若真的需要c++提供了一move方法,将左值转化成了右值。
右值引用核心:
移动语义||资源转移
如果第二个对象是在复制或赋值结束后被销毁的临时对象,则调用移动构造函数和移动赋值运算符,这样的好处是避免深度复制,提高效率。
移动构造和移动赋值配合使用:
string a ,b, string c =() b + c;
b+c 形成一个 tmp 将进行一次拷贝构造 ,这个tmp是一个临时值,这是又使用移动构造,将这个将亡值给偷梁换柱掉。
移动构造
思想:
将一个要销毁的资源移动到需要它的资源里面去,就不用创建临时对象接收将销毁对象的资源,也不用销毁临时对象。
而C++11 正好认为将亡值是一个右值,
那么我们就可以进行右值引用。
举个栗子🌰:
注意看 oprerate + 和 移动构造函数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
namespace N
{
class String
{
public:
String(const char* str = "" )
{
if (str == nullptr)
str = "";
str_ = new char[strlen(str) + 1];
strcpy(str_, str);
}
String(const String& s)
:str_ (new char[strlen(s.str_) + 1])
{
strcpy(str_, s.str_);
}
//右值引用 : 1 将亡值 2 右值
//移动构造 :把将亡值的资源移动出来
String(String&& a)
:str_(a.str_)
{
a.str_ = nullptr;//当然也可以swap
}
//移动语义||资源转移
//仔细分析这个函数做了什么
//s3 = s2 + s1; 内部创建了一个ret; 传值值返回,右边需要一个temp接收s1 + s2值,
//即 s2 + s1 --->temp生成; s3 = temp; temp 销毁 所以总共创建了3次,销毁了两次
String operator + (const String& a)
{
int len1 = strlen(str_);
char* tmp = new char[len1 + strlen(a.str_) + 1];
strcpy(tmp, str_);
strcpy(tmp + len1, a.str_);
String ret(tmp);//创建一份局部对象
delete[] tmp;//防止内存泄漏
return ret;//返回局部对象后,便被销毁 ,在返回值那里 又创建一个Temp临时对象接收返回值,
}
String& operator = (const String& s)
{
//先申请空间,避免申请空间失败了,又把原来的内容删除了
char* tmp = new char[strlen(s.str_) + 1];
strcpy(tmp, s.str_);
delete[] str_;
str_ = tmp;
return *this;
}
~String()
{
if (str_)
delete[] str_;
str_ = nullptr;
}
private:
char* str_;
};
}
int main()
{
N::String a("HELLO");
N::String b(" world");
N::String c = a + b;
//std::cout << c << std::endl;
return 0;
};
看到没资源转移了?避免了刚释放空间又申请空间,大大提高了程序的运行效率!!
完美转发:
所谓完美转发其实就是一个函数模版
实现原理 :
模版函数在接收来不同的类型函数时,按照各自类型的转发给另一个函数去执行, 不产生额外开销!!
不产生额外开销!!
不产生额外开销!!
类似于代理 分类+转发,
就像模版转发函数并不存在似的。完美吧?
注意: 完美转发的模版函数并不更改参数的左右值属性, 目的是因为,让目标函数k可能对原有数据的左右值属性进行不同的处理。
C++11 用 forward函数来实现完美转发。
头文件 #include
C++ lambda表达式
什么是lambda表达式?
在函数中声明函数称为lambda表达式:
eg: int Add(int a, int b , &a mutable-> return-type{ return a++;} )
{ return a + b; }
你可能会问函数名是啥呀?
注意: lambda表达式可以看作是一个无名函数。
为什么提出 lambda表达式?
在一个函数中,可能有部分代码重复出现,导致冗余,解决办法:
1 将重复代码封装程一个函数(有时候,只有一两个函数用,别的函数用不到,所以也别是怎么可观)
2 写个仿函数,但每次调用都要构造一个类,很麻烦,
class Add
{
public:
int operator () (int& a, int &b)
{
return a + b;
}
};
仿函数内部重载一下需要的运算符。
eg: int A(10, 20, Add()){ };
2 方法三: 在函数中声明函数->lambda表达式
lambda表达式 :以【】开始
父作用域: lambda表达式所在的代码块
【】 :不捕获父作用域的成员变量
【=】:按照值的方式捕获父作用域的所用变量,不会影响父作用域
【&】:按照引用的方式捕获父作用域中的所有变量,会影响父作用域
【var】 安值的方式捕获父作用域中的var变量
【this】 捕获父成员中的this 指针
【&var】安引用的方式捕获父作用域中的var变量
参数列表: ()需要则写,不需要时不写
mutable :
lambda表达式返回值默认情况下是const 类型, 加上mutable 可以取消返回值的常量属性,但是前提是必须加上()参数列表。
->return -type :
用追踪返回值类型 来声明函数返回值类型,没有返回值时,可以省略,也可以直接省略,由编译器推导。
因为返回值类型和参数列表可以不写,而【】可以为空,因此C++11中最简单的lambda函数: 【】{ } 啥都不做。
lambda函数可以理解为无名函数,,因为没有名字,所以不能直接调用,因此如果想直接调用,那就需要用auto 一个变量来推导接收返回值类型。
因此这个变量为函数指针类型。
lambda捕获列表不能重复捕获,
lambda放在块作用域以外,捕获列表为空,即什么都捕获不到
lambda表达式只能捕获父作用域中的局部变量
不同类型的lambda表达式之间不能相互赋值
C++11 线程库
库,肯定跨平台
C++11对线程进行了支持,因此c++并行编程不需要依赖第三方库,
而且还**引入了原子类型,**很多情况可以避免了加锁带来的消耗。
#include
该头文件声明了std::thread线程类
1 线程的启动
C++线程库构造一个对象来启动一个线程,该对象包含了线程运行时上下文信息,eg:线程函数,线程栈,线程起始状态线程id等所有操作全部封装在一起,最后在底层统一传递给_beginthreadex()创建线程函数来实现。
2 线程的结束
join :加入式:主动等待线程终止,,join会清理线程资源,然后返回,由于join已经清理了资源,thread对象与已销毁的线程就没有任何关系了,因此一个线程对象每次只能使用一次join()
detach:分离式
detach会从线程中分离出新的线程,之后不能在与新线程交互,detach线程会在后台运行,其所有权和控制权交给C++运行库。
同时c++运行库保证,当线程退出时,线程资源将被回收。
3 原子性操作库atomic
线程最大的问题就是线程安全问题,C++98 手段是加锁,但加锁势必影响程序运行效率
c++11提供了原子操作库。
#include
实质是对数据类型的原子化。
4 mutex 互斥量(锁)
1 基本的std::mutex
lock()
unlock()
trylock()
2 recursive_mutex 递归锁
允许递归的形式多次上锁。
3 std::timed_mutex
try_lock_for
try_lock_until
lock_guard
采用RAII方式,对锁进行封装,是一个类模版。
适合简单的加锁解锁
unique_lock
采用RAII方式