【C++】第二章 新增数据特征

本文详细介绍了C++11的新特性,包括初始化方法、auto关键字的使用、新字符类型如wchar_t、char16_t、char32_t及raw字符串。此外,还讨论了结构体的初始化、指针的内存管理、引用变量的分类(左值引用和右值引用)以及临时变量的作用。文章还涵盖了外部变量的声明以及名称空间的使用,帮助读者理解C++11中的重要更新。
摘要由CSDN通过智能技术生成

该文章内容整理自《C++ Primer Plus(第6版)》、《Effective C++(第三版)》、以及网上各大博客

初始化

C++11中有新的初始化变量方法

int a(100);
//C++11中可用大括号初始化任何类型,等号可用可不用,这种初始化方式成为列表初始化
int b{100};
int c = {100};
//括号内为空时初始化为0
int d{};
int e = {};   

这种方法可防止缩窄,即禁止将数值赋值给无法存储它的数值变量,但允许转换为更宽的类型。如:

char c1 = 1.57e27;//char c1 {1.57e27}这样就会禁止转换
char c2 = 459585821;//char c2 = {459585821}

auto

C++11中赋予auto一个新的含义。当在初始化声明中使用关键字auto而不指定变量的类型,编译器则会把变量的类型设置成与初始值相同。但是在声明基本类型时通常不用这种方式,在声明STL类型时这种表示方式才会发挥作用

字符类型

C++11除了新增long long和unsigned long long以支持64位整型外,还新增了一些字符类型:

  1. wchar_t。char可以用来表示基本字符集,而wchar_t宽字符类型可以用来扩展字符集,如Unicode。大小为2个或4个字节。cin和cout将输入和输出看作是char流,因此不适合用于处理wchar_t类型,iostream头文件提供了wcin 和wcout用于处理wchar_t流。另外可以通过加上前缀L来指示宽字符常量和宽字符串,如L’a’或L"abc"
  2. char16_t和char32_t。char16_t为无符号2字节,用前缀u表示,如u’a’或u"abc"。char16_t与通用字符名\u00F6匹配。char32_t为无符号4字节,用前缀U表示,如U’a’或U"abc"。char32_t与通用字符名\u00000000F6匹配。使用char16_t和char32_t能表示比Unicode码更多的字符
  3. raw原始字符串。用来表示字符自己,如\n不表示换行符而是仅仅表示反斜杠和字母n。用R"(…)“来表示。另外,当原始字符串中包含)“时,语法允许在”(和)“之间添加任意数量的基本字符(空格、左括号、右括号、斜杆和控制字符除外),如R”+*a()+*a”。同时,可以将前缀R和其他字符串前缀结合使用,R可放在前面或者后面,如Ru、UR等

结构体

在C++中,声明结构体变量时可以省略关键词struct。即在C中声明结构体变量struct Date today;,但在C++中为Date today;。列表初始化同样适用于结构体变量的初始化,如Date today {2020, 1, 1, “Wednesday”};

指针

在C中存储区可分为5个部分:程序代码区、静态存储区、堆、栈、常量存储区。且用malloc分配的空间是在堆中。而在C++中则多一个自由存储区,由new分配。但大多数编译器都默认使用堆来实现自由存储区,所以整体上C++也可以分为5个存储区

new和delete必须匹配使用。new运算符初始化方法有两种。如下

int *a = new int (6);//这种方法适合有合适构造函数的类
int *b = new int[4] {1, 2, 3, 4};//或使用列表初始化

new运算符有一种变体,称为定位new运算符,能够指定要使用的位置,用以设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。使用定位new的例子如下。使用定位new首先要包含new头文件。注意使用同一个空间(地址)创建两个变量时后者会将前者覆盖。另外,这里的buf1是在静态存储区中,因而new所得的指针最后不能用delete进行释放,而当定位new指向的空间是由new创建时则需要用delete进行释放

struct chaff {
    char dross[20];
    int slag;
};
char buf1[50];
char buf2[500];
int main() {
	chaff * p1;
	int * p2;
	p1 = new (buf1) chaff;
	p2 = new (buf2) int[20];
	...
}

对于动态数组,new的一般形式为type * p = new type[n],而delete的一般形式则为delete [] p。而对于多维数组,有两种new的方式

int (* array2D)[5] = new int[height][5];  //当做数组指针处理
delete[] array2D; 
    
int **array2D = new int *[height];  
for(int i=0; i<height; ++i) array2D[i] = new int[width];
for(int i=0; i<height; ++i) delete[] array2D[i];  
delete[] array2D;  

对于char a[]和char *b,&a和a数值上一样,但是&b和b数值上不同,&b是指针的地址。若要输出b的值(也即字符串的地址),在C中可以使用%p以地址形式输出,而在C++中cout则会自动辨别类型,此时需要使用强制类型转换(int *)b再用cout输出地址

C++中认为函数指针p和*p等价。也即p()和(*p)()都是调用函数指针p所指向的函数

在C中可以用0或NULL表示空指针,而在C++则提供了关键字nullptr来表示

C++11中新增三种智能指针unique_ptr、shared_ptr、weak_ptr,定义在头文件<memory>中,可以对动态资源进行管理,保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用

  • unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,可指定其他操作)
unique_ptr<int> up1(new int(11));
//unique_ptr<int> up2 = up1;   // err, 不能通过编译
unique_ptr<int> up3 = std::move(up1);  //现在p3是数据的唯一的unique_ptr
up3.reset();     // 显式释放内存
up1.reset();     // 不会导致运行时错误

unique_ptr<int> up4(new int(22));
up4.reset(new int(44)); //"绑定"动态对象
up4 = nullptr;//显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价

unique_ptr<int> up5(new int(55));
int *p = up5.release(); //只是释放控制权,不会释放内存
//cout << *up5 << endl; // err, 运行时错误
delete p; //释放堆区资源
  • shared_ptr允许多个该智能指针共享“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除
shared_ptr<int> sp1(new int(22));
shared_ptr<int> sp2 = sp1;
cout << sp2.use_count() << endl; // 打印引用计数
sp1.reset(); // 显示让引用计数减一
  • weak_ptr是为协助shared_ptr工作而引入的一种智能指针,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。没有重载 * 和 -> 但可以使用lock获得一个可用的shared_ptr对象。weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。注意weak_ptr并不拥有资源的所有权,所以不能直接使用资源。可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权
shared_ptr<int> sp1(new int(22));
weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象
cout << wp.use_count() << endl;
shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>

引用变量

(1). 左值引用
传统的C++11引用又称左值引用。引用变量是指引用已定义的变量的别名。使用&来声明引用,使得引用变量与其指向的已定义变量指向相同的值和内存单元。引用变量主要用作函数的形参,函数将直接使用原始数据作为参数而不是其副本,此时这种传递方式称为按引用传递。引入变量主要是为了结构和类而不是基本的内置类型

int a;
int & b = a;

引用和指针的区别之一是引用变量在声明时必须要初始化,而指针则可以先声明后赋值

当引用变量作为函数参数时不存在类型的隐式转换,因而参数是什么类型就必须传什么类型

函数可以返回引用类型,但不能返回已释放的内存空间的引用。此时func() = a;成立,因为赋值语句的左值是引用变量,要想禁止这种赋值方式,需要将函数的返回值设成const的引用变量

(2). 右值引用
右值引用使用&&表示,可关联到右值。如:

int x = 10, y = 23;
int && r1 = 13;
int && r2 = x + y;//r2关联的是相加的结果,即使x或y的值改变的也不会影响到r2

C++11引入右值引用的主要目的是实现移动语义。在将含大量元素的临时变量复制给其他变量后系统会删除临时变量,此时会做大量无用功。而移动语义是指不将元素复制到新的地方后删除原来位置的元素,而是将元素留在原来的位置,并将新变量与这个位置关联。移动语义避免了移动原始数据,而只是修改了记录

//左值引用编写复制构造函数
TestClass(const TestClass & f) : n(f.n) {
    v = new char[n];
    for(int i = 0; i < n; ++i) v[i] = f.v[i];
}

//右值引用编写移动构造函数(此时参数为如a+b后返回的临时对象,即右值)
TestClass(TestClass && f) : n(f.n) {//修改了f,不能为const
    v = f.v;
    f.n = 0;
    f.v = nullptr;//避免delete[]该内存空间两次,所以要将原来的指针设为空指针,这种夺取所有权的方式称为窃取
}

只有当为右值时才会调用移动构造函数或移动赋值函数,若为左值时要想调用右值相关的函数则需要用static_cast<>将对象的类型强制转换成TestClass &&。而在C++11中则在头文件<utility>提供了函数move(),强制使用右值转移

另外,右值引用并非是让用户编写右值引用的代码,而是能够使用右值引用实现的移动语义库代码。如STL类中都有复制构造函数、移动构造函数、复制赋值运算符、和移动赋值运算符

临时变量

C++中真正意义上的临时变量是看不见的,它通常会在类型转换(赋值语句或传参时)以及函数返回值时被创建。临时变量的声明周期很短,当表达式结束之后就会被销毁。但当这个临时变量用来初始化一个引用的话则该临时变量的生命周期就会延长
在当函数参数传递发生类型转换,或用不同的类型对引用变量进行初始化时,如变量类型为const string&,但传入的变量类型为char[],此时就会创建临时变量。同时,当发生类型转换时引用变量必须为const类型
引用临时变量的其中一种用法就是不同函数返回同一基类的不同子类,用来给基类的const类型的引用变量进行初始化,这样就能用同一类型的基类调用不同子类对象

外部变量

C++新增了作用域解析运算符::,放在变量民前面,表示此时使用的变量的全局版本而非局部版本

名称空间

声明区域:是指可在其中进行声明的区域
潜在作用域:指从生命点开始,到其声明区域的结尾。因而潜在作用域比声明区域小。然而变量并非在其潜在作用域的任何位置都是可见的,如潜在作用域内嵌套了一个同名局部变量

C++新增了一种功能,通过定义一种新的声明区域来创建命名的名称空间,使得一个名称空间中的名称不会与另一个名称空间的相同名称发生冲突。名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。默认情况下名称空间中声明的名称的链接性为外部的。名称空间是开放的,即可在多个地方将不同对象加入到同一个名称空间中

namespace Jack {
    void fetch();
    int pal;
    namespace Jackson {...}
}
namespace Jill {
    double pail;
    int pal;
    struct Well {...};
}

访问名称空间最简单的方法是通过作用域解析运算符::,如Jack::pail = 12.34;
若不希望每次访问变量都需要对其进行限定,可用using声明和using编译指令
using声明可将特定名称添加到声明语句所属的声明区域中,如using Jack::pail;。此后其声明区域中可直接使用pail变量而不需要添加限定
using编译指令则使得所有名称都可用,如using namespace Jack;

名称空间能嵌套声明,即一个名称空间的声明可以在另一个名称空间的声明里面,此时作用域解析运算符也嵌套使用。另外,还可以给名称空间创建别名

namespace J = Jack;
//或用于简化嵌套名称空间
namespace JS = Jack::Jackson;
using JS;

名称空间可以省略名称,此时不能显式地使用using来声明,并且只能在未命名名称空间声明所在文件内使用这些对象。因而未命名名称空间也是链接性为内部的静态变量的替代品

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值