1.c++版本迭代
第1节 explicit 关键字
explicit /ɪkˈsplɪsɪt/ 明确的;清楚的;直率的;详述的
作用是表明该构造函数是显示的, 而非隐式的.不能进行隐式转换! 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
#include <iostream>
#include <string>
using namespace std;
class student {
public:
student(int _age)
{
age = _age;
cout << "age=" << age << endl;
}
student(int _age, const string _name)
{
age = _age;
name = _name;
cout << "age=" << age << "; name=" << name << endl;
}
~student()
{
}
int getAge()
{
return age;
}
string getName() {
return name;
}
private:
int age;
string name;
};
int main(void) {
student xiaoM(18); //显示构造
student xiaoW = 18; //隐式构造
//student xiaoHua(19, "小花"); //显示构造
//student xiaoMei = { 18, "小美" }; //隐式构造 初始化参数列表,C++11 前编译不能通过,C++11新增特性
system("pause");
return 0;
}
第2节 左值和右值的概念
- 存储的层次结构
左值和右值的概念
按字面意思,通俗地说。以赋值符号 = 为界,= 左边的就是左值(lvalue),= 右边就是右值(rvalue)。
int a = 666;
左值 右值
int b = 888;
左值 右值
int c = a + b;
左值 右值 右值
lvalue - 代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。
rvalue - 通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的
lvalue的定义,rvalue是在不在内存中占有确定位置的表达式,而是存在在寄存器中。
所有的左值(无论是数组,函数或不完全类型)都可以转换成右值。
第3节 函数返回值当引用
C++引用使用时的难点:
1.当函数返回值为引用时
若返回栈变量,不能成为其它引用的初始值,不能作为左值使用
(本质的来讲就是局部变量无法当作引用)
2.若返回静态变量或全局变量
可以成为其他引用的初始值
即可作为右值使用,也可作为左值使用
3.返回形参当引用
(注:C++链式编程中,经常用到引用,运算符重载专题知识)
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int demo1() {
int i = 0;
//printf("i 的地址: %p, i=%d\n", &i, i);
return i;
}
int &demo(int **addr) {
int i = 666;
*addr = &i;
printf("i 的地址: %p, i=%d\n", &i, i);
return i;
}
int &demo_static(int **addr) {
static int i = 666;
*addr = &i;
printf("demo_static: i 的地址: %p, i=%d\n", &i, i);
return i;
}
//3. 函数返回形参(普通参数)当引用
int &demo3(int var) {
var = 666;
return var;
}
//4. 函数返回形参(引用)当引用
int &demo4(int &var) {
var = 666;
return var;
}
int main(void) {
int *addr = NULL;
int ret = demo(&addr);
//第一种情况 函数返回局部变量引用不能成为其它引用的初始值
//int &i1 = demo(&addr);
//i1 = 888;
//printf("addr: %p i1=%d\n", addr, i1);
//demo(&addr);
//demo1();
//printf("addr: %p i1=%d\n", addr, i1);
//第二种情况 函数返回局部变量不能做为左值
/*demo(&addr) = 888;
printf("1. addr: %p value: %d\n", addr, *addr);
demo1();
printf("2. addr: %p value: %d\n", addr, *addr);
*/
//第三种情况 返回静态变量或全局变量可以成为左值或是其它引用的初始值
//demo_static(&addr) = 888;
int &i1 = demo_static(&addr);
i1 = 888;
printf("1. addr: %p value: %d\n", addr, *addr);
demo1();
printf("2. addr: %p value: %d\n", addr, *addr);
demo_static(&addr);
system("pause");
return 0;
}
第4节 C++11 新增容器 - array
array容器概念
- array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。
- array是将元素置于一个固定数组中加以管理的容器。
- array可以随机存取元素,支持索引值直接存取, 用[]操作符或at()方法对元素进行操作,也可以使- 用迭代器访问
- 不支持动态的新增删除操作
- array可以完全替代C语言中的数组,使操作数组元素更加安全!
- 头文件 #include
array特点
- array 容器的大小是固定的,无法动态的扩展或收缩,这也就意味着,在使用该容器的过程无法增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。
- STL 还提供有可动态扩展或收缩存储空间的 vector 容器
array对象的构造
array采用模板类实现,array对象的默认构造形式
array<T,int> arrayT; //T为存储的类型, 为数值型模板参数
//构造函数
array<int, 5> a1; //一个存放5个int的array容器
array<float, 6> a2; //一个存放6个float的array容器
array<student, 7> a3; //一个存放7个student的array容器
array的赋值
a1.assign(0); //玩法一 改变array中所有元素(注:将被废弃,不推荐使用)
a1.fill(666); //玩法二 用特定值填充array中所有元素
array<int, 4> test={1, 2, 3, 4};// 玩法三 定义时使用初始化列表
array<int, 4> test;
test={1,2,3,4}; //玩法四 定义后使用列表重新赋值
array<int, 4> a1,a2;
a1={1,2,3,4};
a2 = a1;//玩法五,赋值运算
a1.swap(a2); //玩法六 和其它array进行交换
array的大小
array.size(); //返回容器中元素的个数
array.empty(); //判断容器是否为空,逗你玩的,永远为 false
array.max_size(); //返回容器中最大元素的个数,同size()。
array的数据存取
第一 使用下标操作 a1[0] = 100;
第二 使用at 方法 如: a1.at(2) = 100;
第三 接口返回的引用 a1.front() 和 a1.back()
注意: 第一和第二种方式必须注意越界
array 迭代器访问
array.begin(); //返回容器中第一个数据的迭代器。
array.end(); //返回容器中最后一个数据之后的迭代器。
array.rbegin(); //返回容器中倒数第一个元素的迭代器。
array.rend(); //返回容器中倒数最后一个元素的后面的迭代器。
array.cbegin(); //返回容器中第一个数据的常量迭代器。
array.cend(); //返回容器中最后一个数据之后的常量迭代器。
array.crbegin(); //返回容器中倒数第一个元素的常量迭代器。
array.crend(); //返回容器中倒数最后一个元素的后面的常量迭代器。
array<int, 5> arrayInt = {1, 2, 3, 4, 5};
//顺序输出 1 2 3 4 5
for(array<int, 5>::iterator it=arrayInt .begin(); it!=arrayInt .end(); ++it)
{
int elem = *it;
cout << elem; //或直接使用cout << *it
}
set.rbegin()与set.rend()。
第5节 C++的类型转换
1.C++类型转换详解
- 旧式转型 C风格的强制类型:
TYPE b = (TYPE) a
例如:
int i = 48;
char c = (char) i; - 新式转型C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。
格式:
TYPE b = 类型操作符 ( a )
类型操作符= static_cast | reinterpreter_cast | dynamic_cast | const_cast
2.static_cast
静态类型转换(斯文的劝导,温柔的转换)。如int转换成char
- 主要用法:
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。上行指针或引用(派生类到基类)转换安全,下行不安全
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型。
#include <iostream>
using namespace std;
class Animal {
public:
virtual void cry() = 0;
};
class Cat :public Animal
{
public:
void cry()
{
cout << "喵喵瞄" << endl;
}
};
class Dog :public Animal
{
public:
void cry()
{
cout << "汪汪汪" << endl;
}
};
int main(void) {
//第一种情况 父子类之间的类型转换
Dog* dog1 = new Dog();
Animal* a1 = static_cast<Animal*>(dog1); //子类的指针转型到父类指针
Dog* dog1_1 = static_cast<Dog*>(a1); //父类的指针转型到子类的指针
Cat* cat1 = static_cast<Cat*>(a1); //父子到子类,有风险,这样是不行的,会出问题
Dog dog2;
Animal& a2 = static_cast<Animal&>(dog2); //子类的引用转型到父类的引用
Dog &dog2_2 = static_cast<Dog&>(a2); //父类到子类引用
//第二种 基本类型的转换
int kk = 234;
char cc = static_cast<char>(kk);
//第三种 把空指针转换成目标类型的空指针。
int* p = static_cast<int*>(NULL);
Dog* dp = static_cast<Dog*>(NULL);
//第四种 把任何类型的表达式转换成void类型
int* pi = new int[10];
void* vp = static_cast<void*>(pi);
vp = pi;
system("pause");
return 0;
}
3.reinterpret_cast
重新解释类型(挂羊头,卖狗肉) 不同类型间的互转,数值与指针间的互转
用法: TYPE b = reinterpret_cast ( a )
TYPE必须是一个指针、引用、算术类型、函数指针.
忠告:滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。
#include <iostream>
using namespace std;
class Animal {
public:
void cry() {
cout << "动物叫" << endl;
}
};
class Cat :public Animal
{
public:
void cry()
{
cout << "喵喵瞄" << endl;
}
};
class Dog :public Animal
{
public:
void cry()
{
cout << "汪汪汪" << endl;
}
};
int main02(void) {
//用法一 数值与指针之间的转换
int* p = reinterpret_cast<int*>(0x99999);
int val = reinterpret_cast<int>(p);
//用法二 不同类型指针和引用之间的转换
Dog dog1;
Animal* a1 = &dog1;
a1->cry();
Dog* dog1_p = reinterpret_cast<Dog*>(a1);
Dog* dog2_p = static_cast<Dog*>(a1); //如果能用static_cast ,static_cast 优先
//Cat* cat1_p = static_cast<Cat*>(a1);
//Cat* cat2_p = static_cast<Cat*>(dog1_p);//NO! 不同类型指针转换不能使用static_cast
Cat* cat2_p = reinterpret_cast<Cat*>(dog1_p);
Animal& a2 = dog1;
Dog& dog3 = reinterpret_cast<Dog&>(a2);//引用强转用法
dog1_p->cry();
dog2_p->cry();
cat2_p->cry();
system("pause");
return 0;
}
4.dynamic_cast
动态类型转换
- 将一个基类对象指针cast到继承类指针,dynamic_cast 会根据基类指针是否真正指向继承类指针来做相应处理。失败返回null,成功返回正常cast后的对象指针;
- 将一个基类对象引用cast 继承类对象,dynamic_cast 会根据基类对象是否真正属于继承类来做相应处理。失败抛出异常bad_cast
- 注意:dynamic_cast在将父类cast到子类时,父类必须要有虚函数。
#include <iostream>
using namespace std;
class Animal {
public:
virtual void cry() = 0;
};
class Cat :public Animal
{
public:
void cry()
{
cout << "喵喵瞄" << endl;
}
void play()
{
cout << "爬爬树"<<endl;
}
};
class Dog :public Animal
{
public:
void cry()
{
cout << "汪汪汪" << endl;
}
void play()
{
cout << "溜达溜达" << endl;
}
};
void animalPlay(Animal& animal) {
animal.cry();
try {
Dog& pDog = dynamic_cast<Dog&>(animal);
pDog.play();
}
catch (std::bad_cast bc) {
cout << "不是狗,那应该是猫" << endl;
}
try {
Cat& pCat = dynamic_cast<Cat&>(animal);
pCat.play();
}
catch (std::bad_cast bc) {
cout << "不是猫,那应该是上面的狗" << endl;
}
}
void animalPlay(Animal* animal) {
animal->cry();
Dog* pDog = dynamic_cast<Dog*>(animal);
if (pDog) {
pDog->play();
}
else {//pDog == NULL
cout << "不是狗,别骗我!" << endl;
}
Cat* pCat = dynamic_cast<Cat*>(animal);
if (pCat) {
pCat->play();
}
else {//pDog == NULL
cout << "不是猫,别骗我!" << endl;
}
}
int main(void) {
Dog* dog1 = new Dog();
Animal* a1 = dog1;
//animalPlay(a1);
Dog dog2;
animalPlay(dog2);
Cat* cat1 = new Cat();
Animal* a2 = cat1;
//animalPlay(a2);
Cat cat2;
animalPlay(cat2);
system("pause");
return 0;
}
5.const_cast
去const属性。(仅针对于指针和引用)
#include <iostream>
using namespace std;
void demo(const char* p)
{
//对指针去掉cost 重新赋值
//char* p1 = const_cast<char *>(p);
//p1[0] = 'A';
//直接去掉const修改
const_cast<char*>(p)[0] = 'A';
cout << p << endl;
}
void demo(const int p)
{
int q = p;
//const_cast<int>(p) = 888;// NO ! 不能对非指针和引用进行const 转换
cout << p << endl;
}
int main(void)
{
//字符串数组
//char p[] = "12345678";
//demo(p); //合情合理
//常量字符串不能去掉const 修改
//警告: 在去掉常量限定符之前,保证指针所指向的内存能够修改,不能修改则会引起异常。
const char* cp = "987654321";
demo(cp);
system("pause");
return 0;
}
6.类型转换使用建议
-
static_cast静态类型转换,编译的时c++编译器会做编译时的类型检查;隐式转换;
基本类型转换,父子类之间合理转换 -
若不同类型之间,进行强制类型转换,用reinterpret_cast<>() 进行重新解释
建 议:
C语言中 能隐式类型转换的,在c++中可用 static_cast<>()进行类型转换。因C++编译器在编译检查一般都能通过;C语言中不能隐式类型转换的,在c++中可以用 reinterpret_cast<>() 进行强制类型解释。
总结:static_cast<>()和reinterpret_cast<>() 基本上把C语言中的 强制类型转换给覆盖,注意reinterpret_cast<>()很难保证移植性。
- dynamic_cast<>(),动态类型转换,安全的虚基类和子类之间转换;运行时类型检查
- const_cast<>(),去除变量的只读属性
最后的忠告:程序员必须清楚的知道: 要转的变量,类型转换前是什么类型,类型转换 后是什么类型,转换后有什么后果。
第6节 C++智能指针
1.为什么要使用智能指针
先看一个例子
#include <iostream>
#include <string>
#include <exception>
using namespace std;
void memory_leak_demo1() {
string* str = new string("今天又敲了一天代码,太累了,回家休息了!!!");
cout << *str << endl;
return;
}
int memory_leak_demo2() {
string* str = new string("这个世界到处是坑,所以异常处理要谨记在心!!!");
/***********************************************
* 程序执行一段复杂的逻辑,假设尝试从一个必须存在
* 的文件中读取某些数据,而文件此时不存在
************************************************/
{
throw exception("文件不存在");
}
cout << *str << endl;
delete str;
return 0;
}
int main()
{
memory_leak_demo1();
try {
memory_leak_demo2();
}
catch (exception e) {
cout<<"catch exception: "<<e.what()<<endl;
}
system("pause");
return 0;
}
以上两种情况都会出现内存泄漏!
更好的解决方案: 把string 定义为auto 变量,在函数生命周期结束时释放!
void memory_leak_demo1() {
string str("今天又敲了一天代码,太累了,回家休息了!!!");
cout << str << endl;
return;
}
int memory_leak_demo2() {
string str("这个世界到处是坑,所以异常处理要谨记在心!!!");
/***********************************************
* 程序执行一段复杂的逻辑,假设尝试从一个必须存在
* 的文件中读取某些数据,而文件此时不存在
************************************************/
{
throw exception("文件不存在");
}
cout << str << endl;
return 0;
}
-
思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这看似是一个 very nice 的方案?
-
智能指针就是通过这个原理来解决指针自动释放的问题!
-
C++98 提供了 auto_ptr 模板的解决方案
-
C++11 增加unique_ptr、shared_ptr 和weak_ptr
2.auto_ptr 使用详解 (C++98)
auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!
用法:
头文件: #include
用 法: auto_ptr<类型> 变量名(new 类型)
例 如:
auto_ptr str(new string(“我要成为大牛~ 变得很牛逼!”));
auto_ptr<vector> av(new vector(10));
#include <iostream>
#include <string>
#include <exception>
#include <memory>
using namespace std;
//auto_ptr< Test> t(new Test()); //忠告1: 智能指针不要定义为全局变量
class Test
{
public:
Test() {
cout << "Test is construct" << endl;
debug = 1;
}
~Test() { cout << "Test is destruct" << endl; }
int getDebug() {
return debug;
}
private:
int debug;
};
//用 法: auto_ptr<类型> 变量名(new 类型)
void memory_leak_demo1() {
auto_ptr< Test> t(new Test());
//忠告3: 除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个智能指针
//auto_ptr< Test> t1;
//t1 = t;
//auto_ptr<Test>* tp = new auto_ptr<Test>(new Test()); //忠告2: 不要定义指向智能指针对象的指针变量
//在使用智能指针访问对象时,使用方式和普通指针一样
cout<< "-> debug: "<<t->getDebug()<< endl;
cout << "* debug: " << (*t).getDebug() << endl;
//Test* tmp = t.get();
//cout << "get debug: " << tmp->getDebug() << endl;
//release 取消指针指针对动态内存的托管,之前分配的内存必须手动释放
//Test* tmp = t.release();
//delete tmp;
//reset 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
//t.reset();
t.reset(new Test());
if(0){
Test* t1 = new Test();
t1->getDebug();
}
return;
}
int memory_leak_demo2() {
//Test* t = new Test();
auto_ptr< Test> t(new Test());
/***********************************************
* 程序执行一段复杂的逻辑,假设尝试从一个必须存在
* 的文件中读取某些数据,而文件此时不存在
************************************************/
{
throw exception("文件不存在");
}
//delete t;
return 0;
}
int main()
{
memory_leak_demo1();
/*try {
memory_leak_demo2();
}
catch (exception e) {
cout << "catch exception: " << e.what() << endl;
}*/
system("pause");
return 0;
}
- 使用建议:
1.尽可能不要将auto_ptr 变量定义为全局变量或指针
2.除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个 智能指针
3.C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!
3.unique_ptr 使用详解 (C++11)
- auto_ptr是用于C++11之前的智能指针。由于 auto_ptr 基于排他所有权模式:两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr 主要有两大问题:
- 复制和赋值会改变资源的所有权,不符合人的直觉。
- 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
- 不支持对象数组的操作
#include <stdio.h>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
int main() {
//弊端1. auto_ptr 被C++11 抛弃的主要理由 p1= p2 ,复制或赋值都会改变资源的所有权
auto_ptr<string> p1(new string("I 'm martin."));
auto_ptr<string> p2(new string("I 'm rock."));
printf("p1: %p\n", p1.get());
printf("p2: %p\n", p2.get());
p1 = p2;
printf("after p1 = p2\n");
printf("p1: %p\n", p1.get());
printf("p2: %p\n", p2.get());
//弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
vector<auto_ptr<string>> va;
auto_ptr<string> p3(new string("I 'm p3."));
auto_ptr<string> p4(new string("I 'm p4."));
va.push_back(std::move(p3));
va.push_back(std::move(p4));
cout <<"va[0]: "<< *va[0] << endl;
cout <<"va[1]: "<< *va[1] << endl;
//风险来啦
va[0] = va[1];
cout << "va[0]: " << *va[0] << endl;
cout << "va[1]: " << *va[1] << endl;
//弊端3. 不支持对象数组的内存管理
//auto_ptr<int[]> ai(new int[5]); //不能这样定义
//auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr 变量去管理
/*{
auto_ptr<string> p2;
string* str = new string("智能指针的内存管理陷阱");
p2.reset(str);
{
auto_ptr<string> p1;
p1.reset(str);
}
cout <<"str: " << *p2 << endl;
}*/
system("pause");
return 0;
}
所以,C++11用更严谨的unique_ptr 取代了auto_ptr!
unique_ptr特性
- 基于排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时 右值赋值构造和赋值
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
- 在容器中保存指针是安全的
#include <stdio.h>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
int main() {
//弊端1. auto_ptr 被C++11 抛弃的主要理由 p1= p2 ,复制或赋值都会改变资源的所有权
//unique_ptr 如何解决这个问题? 不允许显示的右值赋值和构造
unique_ptr<string> p1(new string("I 'm martin."));
unique_ptr<string> p2(new string("I 'm rock."));
printf("p1: %p\n", p1.get());
printf("p2: %p\n", p2.get());
//如果一定要转移,使用move 把左值转成右值
p1 = std::move(p2);
printf("p1: %p\n", p1.get());
printf("p2: %p\n", p2.get());
//p1 = p2; //左值赋值禁止
unique_ptr<string> p3(new string("I 'm p3."));
unique_ptr<string> p4(std::move(p3)); //左值拷贝构造也不行,必须转成右值
//弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
vector<unique_ptr<string>> vu;
unique_ptr<string> p5(new string("I 'm p5."));
unique_ptr<string> p6(new string("I 'm p6."));
vu.push_back(std::move(p3));
vu.push_back(std::move(p4));
cout << "va[0]: " << *vu[0] << endl;
cout << "va[1]: " << *vu[1] << endl;
//vu[0] = vu[1]; //unique_ptr不支持直接赋值,没有风险
//弊端3. auto_ptr不支持对象数组的内存管理,unique_ptr 支持
//但是unique_ptr 支持对象数组的管理
//auto_ptr<int[]> ai(new int[5]); //不能这样定义
unique_ptr<int[]> ui(new int[5]); //自动会调用 delete []函数去释放
system("pause");
return 0;
}
构造函数
unique_ptr<T> up ; //空的unique_ptr,可以指向类型为T的对象
unique_ptr<T> up1(new T()) ;//定义unique_ptr,同时指向类型为T的对象
unique_ptr<T[]> up ; //空的unique_ptr,可以指向类型为T[的数组对象
unique_ptr<T[]> up1(new T[]) ;//定义unique_ptr,同时指向类型为T的数组对象
unique_ptr<T,D> up(); //空的unique_ptr,接受一个D类型的删除器d,使用d释放内存
unique_ptr<T,D> up(new T()); //定义unique_ptr,同时指向类型为T的 类型的删除器d,使用删除器d来释放内存赋值
unique_ptr<int> up1(new int(10));
unique_ptr<int> up2(new int(11));
up1 = std::move(up2);//必须使用移动语义,结果,up1 内存释放, up2 交由up1 管理主动释放对象
up = nullptr ;//释放up指向的对象,将up置为空或 up = NULL; //作用相同
放弃对象控制权
up.release(); //放弃对象的控制权,返回指针,将up置为空,不会释放内存
重置
up.reset(…) ; //参数可以为 空、内置指针,先将up所指对象释放,然后重置up的值
交换
up.swap(up1); //将智能指针up 和up1管控的对象进行交换
4.shared_ptr 使用详解 (C++11)
熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?
如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!
构造函数
shared_ptr<T> sp ; //空的shared_ptr,可以指向类型为T的对象
shared_ptr<T> sp1(new T()) ;//定义shared_ptr,同时指向类型为T的对象
shared_ptr<T[]> sp2 ; //空的shared_ptr,可以指向类型为T[的数组对象 C++17后支持
shared_ptr<T[]> sp3(new T[]{...}) ;//指向类型为T的数组对象 C++17后支持
shared_ptr<T> sp4(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D
释放内存
shared_ptr<T> sp5(new T(), D()); //定义shared_ptr,指向类型为T的对象,接受一个D 类型的删除器,使用D删除器来释放内存
初始化
方式一 构造函数
shared_ptrr<int> up1(new int(10)); //int(10) 的引用计数为1
shared_ptrr<int> up2(up1); //使用智能指针up1构造up2, 此时int(10) 引用计数为2
方式二 使用make_shared 初始化对象,分配内存效率更高
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr; 用法:
make_shared<类型>(构造类型对象需要的参数列表);
shared_ptr<int> p4 = make_shared<int>(2); //多个参数以逗号','隔开,最多接受十个
shared_ptr<string> p4 = make_shared<string>("字符串");
赋值
shared_ptrr<int> up1(new int(10)); //int(10) 的引用计数为1
shared_ptr<int> up2(new int(11)); //int(11) 的引用计数为1
up1 = up2;//int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)
的引用计数为2
主动释放对象
shared_ptrr<int> up1(new int(10));
up1 = nullptr ;//int(10) 的引用计数减1,计数归零内存释放
或
up1 = NULL; //作用同上
重置
up.reset() ; //将p重置为空指针,所管理对象引用计数 减1
up.reset(p1); //将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
up.reset(p1,d); //将p重置为p(的值),p 管控的对象计数减1并使用d作为删除器
交换
std::swap(p1,p2); //交换p1 和p2 管理的对象,原对象的引用计数不变
p1.swap(p2); //同上
使用陷阱
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!
#include <stdio.h>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
class girl;
class boy {
public:
boy() {
cout << "boy construct!" << endl;
}
~boy() {
cout << "boy destruct!" << endl;
}
void set_girl_friend(shared_ptr<girl> &g) {
girl_friend = g;
}
private:
shared_ptr<girl> girl_friend;
};
class girl {
public:
girl() {
cout << "girl construct!" << endl;
}
~girl() {
cout << "girl destruct!" << endl;
}
void set_boy_friend(shared_ptr<boy> &b) {
boy_friend = b;
}
private:
shared_ptr<boy> boy_friend;
};
void use_trap() {
shared_ptr<girl> sp_girl(new girl());//白娘子
shared_ptr<boy> sp_boy(new boy()); //许仙
sp_girl->set_boy_friend(sp_boy);
sp_boy->set_girl_friend(sp_girl);
}
int main() {
use_trap();
system("pause");
return 0;
}
5.weak_ptr 使用详解 (自从C++11)
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少. 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。
6.智能指针的使用陷阱
- 不要把一个原生指针给多个智能指针管理
int *x = new int(10);
unique_ptr<int> up1(x);
unique_ptr<int> up2(x);
//警告! 以上代码使up1 up2指向同一个内存,非常危险
或以下形式:
up1.reset(x);
up2.reset(x);
- 记得使用u.release()的返回值
在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了
- 禁止delete 智能指针get 函数返回的指针
如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!
- 禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
shared_ptr sp1(new int(10));
//一个典型的错误用法 shared_ptr sp4(sp1.get());
第7节 结构体内存对齐
#include <stdio.h>
#include <stdlib.h>
using namespace std;
struct A{
char c;
int i;
};
struct B{
char c;
int i;
double d;
};
struct C{
char c;
int i;
double d;
char c1;
};
int main(){
printf("sizeof(A): %d\n", sizeof(struct A));
printf("sizeof(B): %d\n", sizeof(struct B));
printf("sizeof(C): %d\n", sizeof(struct C));
system("pause");
return 0;
}
以上输出的结果并非实际成员占用的字节数,这就是结构体的内存对齐!
结构体内存对齐原因
-
平台原因(移植原因):
“不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常”。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。 -
效率原因:
正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法,但这种做法是值得的。
结构体对齐规则
-
第一个成员在结构体变量偏移量为0 的地址处,也就是第一个成员必须从头开始。
-
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数为编译器默认的一个 对齐数与该成员大小中的较小值。vs中默认值是8 Linux默认值为4(可以通过#pragma pack (N) 修改,使用#pragma pack(show) 可以查看对齐值),但修改时N的取 值只能设置成1, 2,4,8,16.
-
结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
-
如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。
结构体对齐示例