C++11新特性
explicit关键字
explicit关键字是给构造函数使用的,作用是限制构造函数只能使用显示构造,而不能进行隐式转换。比如使用等于号或者初始值列表之类的。构造函数默认是implicit的,我们可以通过使用explicit来限制这个转换,好处是可以提高程序的可读性。
#include <iostream>
using namespace std;
class student {
public:
explicit student(int age = 10) { this->age = age; }
protected:
int age;
};
int main(void) {
student s = 100;//这样是不被允许的
return 0;
}
左值和右值的概念
顾名思义,左值就是可以放在赋值运算符左边的值,而不能放在左边的值则统称为右值。具体的来说,左值是一个拥有内存空间的对象,是可以寻址的,而右值只是一个字面值,它不是存储在内存中的,而是放在了CPU的寄存器中。我们是不能去改变寄存器中数据的值,我们只有内存的操作权限。而左值其实也是可以当作右值来使用的,因为我们可以将左值中的数据加载到CPU的寄存器中,反之则不行。
函数返回引用类型
返回引用通常分为三种情况:
- 返回非静态局部变量的引用
- 返回非引用类型的形参的引用
- 返回静态变量的引用
只有第三种是安全的方式,因为静态变量是存储在全局静态区的,生命周期贯穿整个程序。前两种方式是极其危险的,因为当函数结束之后,那块地址就以及不属于原先的对象了,虽然栈中的数据并没有被清除,但是当我们再次调用函数时(任意函数),我们就会去覆盖这些数据。这时我们再去操作这块地址的结果就无法预料了,因为没有人知道这块地址被分配给了谁。
int& fun(int f1,int& f2) {
static int s = 100;
int i = 100;
/*return i;
return f1;*/
//只有下面这两种是安全的,其中一个为静态变量,还有一个本身就是主调函数的变量
return s;
return f2;
}
array容器
array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数,大小也是不可更改的。只不过加入了一些接口以便我们使用。array完全可以替代传统的数组,更加高效和安全!
array<int, 10> arr;
//arr.assign(11); assign已经被弃用,如果非要使用,可以定义一个宏来防止报错
arr.fill(11);//可以使用fill函数来替代assign
for (array<int, 10>::iterator it = arr.begin(); it != arr.end(); ++it) {
cout << *it << " ";
}
arr.max_size();//跟size()一模一样,因为array的大小是不可更改的
cout << endl;
如果要进行赋值操作的话,实例化的类型和大小都必须一一吻合!
类型转化
C++11新增了四种转换类型,分别是:
- static_cast(VALUE)
- reinterpret_cast(VALUE)
- dynamic_cast(VALUE)
- const_cast(VALUE)
static_cast概述
static_cast是一种比较温柔的转换方式,它只能进行基本数据类型或者子类指针到基类指针的上行转换,以及void型的指针向某种类型指针的转换,当然也可以从某种类型的指针转换为空类型的指针。它会在编译前进行检查,如果不符合以上的四种,编译器会报错。
int i=static_cast<int>(3.14);//double转换为int
int * p=NULL;
son * s;
father* f=static_cast<father*>(s);//子类指针转换为父类指针
void * v=static_cast<void*>(p);//int*转换为void*
int *p1=static_cast<int*>(v);//void*转换为int*
reinterpret_cast概述
reinterpret_cast可以进行类型间的强制转换,比如不同的自定义类型之间的转换,或者指针和数值的转换。这种转换编译器是不会进行检查的,因此不论你进行再离谱的转换,它都是可以接受的,但结果是不可预知的。
class human;
class animal;
animal a;
human h=reinterpret_cast<human>(a);//将动物类转换为了人类
int *p=reinterpret_cast<int*>(0x888888);//将数值转换为int*
dynamic_cast概述
dynamic_cast是进行父类指针向子类指针转换的方式,这就是所谓的下行,下行理论上是不安全的。但如果那块地址本身就是特定子类的对象,那就没有问题了,反之就会返回一个空指针,如果是引用的话,就会返回bad_cast的异常。通常用于以父类的指针作为参数来接收子类对象,来进行一般化的处理。(只有多态中才能使用)
class father {
public:
virtual void play() {
cout << "打麻将" << endl;
}
};
class son :public father {
public:
virtual void play()override {
cout << "打球" << endl;
}
};
class daughter :public father {
public:
virtual void play()override {
cout << "逛街" << endl;
}
};
void fun(father* f) {//指针版
son* s = dynamic_cast<son*>(f);
if (s) {//为真证明转换成功
f->play();
}
else {
cout << "this is not son!" << endl;
}
daughter* d = dynamic_cast<daughter*>(f);
if (d) {
f->play();
}
else {
cout << "this is not daughter!" << endl;
}
}
void fun2(father& f) {//引用版
try {
son& s = dynamic_cast<son&>(f);
s.play();
}catch(bad_cast error) {
cout << "this is not son" << endl;
}
try {
daughter& d = dynamic_cast<daughter&>(f);
d.play();
}
catch (bad_cast error) {
cout << "this is not daughter" << endl;
}
}
void main(){
fun(new son());
fun(new daughter());
son s;
daughter d;
fun2(s);
fun2(d);
}
const_cast概述
const_cast是用来去除指针或引用的const限制的,只能用于指针或引用。但使用时要尤为注意,如果指向的对象真的是一个常量,我们是不能去修改它的值的,因为常量是存储在常量区,那里我们是没有权限的。通常用与指向常量的指针指向了非常量的对象。
void const_fun(const int* c) {
*const_cast<int*>(c) = *c * 10;
}
void main(){
const int ci = 10;
int i = 10;
const_fun(&i);
const_fun(&ci);//修改失败,但不会报错
cout << i << endl;
cout << ci << endl;
}
类型转换的使用建议
- 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<>(),去除变量的只读属性,只能用于指针和引用,注意不要去修改真正常量的值
最后的忠告:程序员必须清楚的知道: 要转的变量类型转换前是什么类型,类型转换后是什么类型,转换后有什么后果,建议一般情况下,不建议进行类型转换;避免进行类型转换。
智能指针
为什么使用智能指针
我们在编写程序的过程中常常因为种种原因忘记释放从堆中申请的内存,这会造成严重的内存泄露。于是C++就引入了智能指针的概念,用一个类来替我们托管指针,当离开类对象的作用域时就自动析构它所托管的指针,以此来避免内存泄漏。
智能指针分为四种,分别为auto_ptr,unique_ptr,shared_ptr和weak_ptr。其中auto_ptr是C++98提供的解决方案,后面三种全都是在C++11中提出的,auto_ptr也因为其种种缺陷而被unique_ptr完全替代了。
auto_ptr
#include <iostream>
using namespace std;
int main(void) {
auto_ptr<int> ai1(new int(10));
auto_ptr<int>ai2(new int(100));
cout << *ai1.get() << endl;//取出托管的指针
int *p=ai1.release();//放弃托管权,返回其托管的指针,记得要自己手动delete
cout << *p << endl;
delete p;//进行手动析构
ai1 = ai2;//ai1将自己托管的指针析构,改为托管ai2的指针
cout << *ai1.get() << endl;
ai2.reset(new int(1000));//析构自己托管的指针,改为托管指定参数
cout << *ai2.get() << endl;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-scPOoaqz-1666768370536)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025172859729.png)]
如果我们使用release放弃了托管,就必须记得手动释放内存,不然会造成内存泄漏。
三个缺点:
- 复制和赋值会改变资源的所有权,不符合人的直觉。
- 在 STL 容器中使用 auto_ptr 存在重大风险,因为容器内的元素必需支持可复制 (copy constructable)和可赋值(assignable)。
- 不支持对象数组的操作
unique_ptr
C++11中引入了unique_ptr的解决方案,这其实是对auto_ptr的一个升级,修复了它不能托管对象数组以及和容器不兼容的缺点。
unique_ptr<int> ui1(new int(10));
unique_ptr<int> ui2(new int(100));
unique_ptr<int[]>ui3(new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0});//托管一个数组
unique_ptr<int, del>ui4(new int(1000),del());//使用自定义的删除器
ui1 =move(ui2);//不支持左值赋值和左值构造,必须使用move函数变成右值
cout << *ui1.get() << endl;
int* p = ui3.get();//获得数组的首地址
for (int i = 0; i != 10; ++i) {//遍历数组
cout << *p++ << " ";
}
ui3=NULL;//释放托管的指针
//释放托管的指针
//ui3=nullptr;
//ui3.reset();
cout << endl;
unique_ptr无法进行左值复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值,我们需要借助move函数。
shared_ptr
shared_ptr可以实现共享指针,里面内置了一个引用计数,每多一个shared_ptr对某个对象进行托管,引用计数就会加一。当智能指针析构时,引用计数就会减一,当引用计数为0就会自动析构他们指向的那个共同的对象。
int* p = new int(10);
shared_ptr<int> si1(p);
//构造一个int*,不会增加引用计数,最多接受十个参数,使用这个函数效率可以更高
shared_ptr<int> si2 = make_shared<int>(100);
shared_ptr<int> si3(new int(1000), del());
si1 = si2;
cout << si2.use_count() << endl;//获得智能指针的引用计数
cout << si3.use_count() << endl;
si2.reset(new int(10000));//放弃p的控制权,转为托管这个临时对象
cout << si1.use_count() << endl;
使用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;//托管了girl指针,永不析构
}
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;//托管了boy指针,永不析构
}
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;
}
weak_ptr
为了解决shared_ptr交叉引用问题提出的一个解决方案,使用弱指针不会改变原有的引用计数。我们可以像使用shared指针一样使用弱指针,但不能直接操作托管对象的接口,必须通过lock()函数获得真正的共享指针来操作托管对象接口。
shared_ptr<int> si1(new int(10));
weak_ptr<int> wi = si1;
cout << si1.use_count() << endl;
shared_ptr<int> si2=wi.lock();//获得共享指针
结构体对齐
结构体对齐原因
平台原因(效率原因)
不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。
效率原因
正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法,但这种做法是值得的。
结构体对齐规则
- 第一个成员在结构体变量偏移量为 0 的地址处,也就是第一个成员必须从头开始。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数为编译器默认的一个对齐数与该成员大小中的较小值。vs 中默认值是 8 Linux 默认值为 4(可以通过#pragma pack (N) 修改,使用#pragma pack(show) 可以查看对齐值),但修改时N的取值只能设置成 1, 2,4,8,16。
- 结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)。
- 如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。
#include <iostream>
using namespace std;
struct A {
char c;
int i;
double d;
};
struct B {
char c;
int i;
double d;
char c2;
};
struct C {
char c;
int i;
A a;
double d;
char c2;
};
int main(void) {
cout << sizeof(A) << endl;//16
cout << sizeof(B) << endl;//24
//貌似不包含对象成员的对齐数
cout << sizeof(C) << endl;//40
return 0;
}
using namespace std;
struct A {
char c;
int i;
double d;
};
struct B {
char c;
int i;
double d;
char c2;
};
struct C {
char c;
int i;
A a;
double d;
char c2;
};
int main(void) {
cout << sizeof(A) << endl;//16
cout << sizeof(B) << endl;//24
//貌似不包含对象成员的对齐数
cout << sizeof© << endl;//40
return 0;
}