c++11笔记
Posted 雷乌斯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++11笔记相关的知识,希望对你有一定的参考价值。
decltype:
1、int i = 42, *p = &i, &r = i;
decltype(r + 0) b; //b是一个未初始化的int
decltype(*p) c; //错误,c是int&,必须初始化
如果表达式的内容是解引用操作,则decltype将得到引用类型。
解引用指针可以得到指针所指的对象,而且还能给这个对象赋值。
因此,decltype(*p)的结果类型就是int&,而非int
------------------------------------------------------------------------------------------------------------------------------------------------------------------
2、
decltype((variable)) 的结果永远是引用
decltype(variable) 只有当variable本身是引用时才是引用
加了括号就变成了表达式,decltype作用于表达式时得到的是引用类型。
假如p的类型是int*,因为解引用运算生成左值,所以decltype(*p) 的结果是int&。 而取地址运算符生成右值,所以decltype(&p)的结果是int**,即指向整型指针的指针。
将decltype用于某个函数时,它返回函数类型而非指针类型。因此,需要显示地加上*以表明我们需要返回指针,而非函数本身
unique_ptr<connection, decltype<end_connection>*> p(&c, end_connection);
------------------------------------------------------------------------------------------------------------------------------------------------------------------
string:
size()返回string::size_type,是一个无符号类型的值。
所以不能和负数比较。
n<0时: str.size() < n 的结果几乎肯定是true,因为负数n会自动转换为一个很大的无符号值
string的比较:
⑴如果2个string长度不同,且较短的string1上每个字符都与较长string2对象对应位置上的字符相同,则string1<string2
⑵如果2个string在某些对应位置上不一致,则比较的结果是string对象中第一对相异字符比较的结果。例:"hello" < "hi"
------------------------------------------------------------------------------------------------------------------------------------------------------------------
数组:
在很多用到数组名字的地方,编译器会自动地将其替换为一个指向数组首元素的指针:
int nums[] = 1,2,3;
int *p2 = nums; //等价于 int *p2 = &nums[0];
因此,当使用数组作为auto变量的初始值时,推断得到的类型是 数组元素的类型的指针,而不是数组。
int a[] = 0,1,2;
auto b(a); //b为 整型指针,不是数组
但是使用decltype时,上述转换不会发生:
decltype(a) b = 1,2,3; //正确,b是数组
不能用数组为另一个内置类型的数组赋初始值,也不允许使用vector对象初始化数组,但是可以
用数组来初始化vector:
int int_arr[] = 0,1,2,3;
vector<int> int_vec(begin(int_arr), end(int_arr));
要使用范围for处理多维数组,除了最内侧的循环,其他所有循环的控制变量都应该是引用类型。如果要操作最内侧的元素,最内侧也应该是引用
for(cosnt auto &row : ia)
for(auto col : row)
cout << col <<endl;
编译器初始化row时会自动将这些数组形式的元素转换成指向该数组内首元素的指针,这样得到的row是int*,无法进行遍历
------------------------------------------------------------------------------------------------------------------------------------------------------------------
不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位。
case true:
string file_name;
int ival = 0;
break;
case false:
jval = next_num();
if(fuke_name.empty())
//...
如果要为某个case分支定义并初始化一个变量,应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外。
case a:
//大括号创建块作用域
string file_name = get_file_name();
break;
case b:
if(file_name.empty()) //此时会报错,未定义这个变量
------------------------------------------------------------------------------------------------------------------------------------------------------------------
常量引用
可以用字面值初始化一个常量引用,但是不能初始化非常量引用
const int &r1 = 10;//正确
const int &r2 = 10.1;//错误,类型不同,10.1将转换为一个临时int temp = 10,再给r2,r2的引用是对临时变量的引用,无意义
int &r3 = 10;//错误,非常量音乐
函数的形参同理:
当形参是常量引用时,可以用字面值调用函数,否则需要传对象
void fnc(const string &str)//...
如果不希望改变形参,就使用常量引用形参
,这样可以传字面值。
fnc("Hello");
fnc(a_string_object);//错误
------------------------------------------------------------------------------------------------------------------------------------------------------------------
函数:
1、局部静态对象
在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间所在的函数结束执行也不会对它有影响
size_t count_calls()
static size_t ctr = 0;//调用结束后仍然有效,可以用来统计这个函数被调用了多少次
return ++ctr;
2、引用类型的参数的实参不需要写引用符号,引用类型的返回值在return处不需要写引用符号
声明:const string &func(const string &str)...
调用:func(a_string_object); //没有&
返回:return a_string_object; //没有&
函数不能返回局部对象的引用或者指针,
因为局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了。
因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错。但是如果返回的是局部变量的地址(指针)的话,程序运行后会出错。
因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放了,这样指针指向的内容就是不可预料的内容,调用就会出错。
准确的来说,函数不能通过返回指向栈内存的指针(注意这里指的是栈,返回指向堆内存的指针是可以的)。
一个由c/C++编译的程序占用的内存分为以下几个部分
⑴栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
⑵堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
⑶全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
⑷文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
⑸程序代码区—存放函数体的二进制代码。
3、参数使用引用,避免拷贝,拷贝大的类类型对象或者容器比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作,所以函数通过引用形参访问该类型的对象
如果无须改变参数的内容,可以把形参定义成对常量的引用。
bool isShorter(const string &s1, const string &s2)
return s1.size() < s2.size();
4、允许定义若干个名字相同的函数,但是要求形参列表有“明显的”区别
void fcn(const int i)
void fcn(int i) //重复定义了fcn(int),一个拥有顶层CONST的形参无法和另一个没有顶层CONST的形参区分开
void fcn2(int*)
void fcn2(int* const)
5、因为数组不能被拷贝,所以函数无法返回数组,但是可以返回数组的引用/指针
返回一个长度是10的int数组的指针:
⑴int (*func(int i))[10]..
⑵using arrT = int[10];//或者 typedef int arrtT[10]
arrT* func(int i)..
⑶auto func(int i) -> int(*)[10] ..
⑷int odd[] = 1,2,3,4,5;
decltype(odd)* func(int i).. //数组被decltype后是数组的类型,不是指向首元素的指针类型,加上*后表示指针,正确
6、指向函数的指针:bool (*fnc)(const string &, const string &); //该指针的类型是bool(const string &,const string &)
返回指针的函数:bool *func(const string &, const string &);
函数无须取地址/解引用:
fnc = new_fnc; 等价于 fnc = &new_fnc;
bool b1 = fnc("hello","bye") 等价于 bool b1 = (*fnc)("hellow","bye");
------------------------------------------------------------------------------------------------------------------------------------------------------------------
类
1、inline 内联函数
关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用
与非inline函数不同的是,inline函数必须在调用该函数的每个文本文件中定义。当然,对于同一程序的不同文件,如果inline函数出现的话,其定义必须相同
建议把inline函数的定义放到头文件中
如下风格的函数Foo 不能成为内联函数:
inline void Foo(int x, int y); // inline 仅与函数声明放在一起
void Foo(int x, int y)
而如下风格的函数Foo 则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。
另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间
2、前向声明/超前声明(forward declaration)
class ClassA;//向程序中引入名字ClassA并指明它是一种类类型,在声明后定义前它是一个不完全类型,用于定义指向这种类型的指针/引用,也可以声明(但不能定义)以不完全类型作为参数或者返回值类型的函数
(可解决两个头文件互相include的问题)
3、静态成员/函数
static写在声明处,定义处不用重复写
.CPP文件 int 类名::静态成员 = 10; // 定义(初始化)时不受private和protected访问限制.
使用: 类名::静态成员 或 对象.静态成员 或 对象的指针->静态成员
静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以
class Bar
public:
static int staticVar;
int var;
void foo1(int i=staticVar);//静态成员做默认参数
void foo2(int i=var);//错!
静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以
class Bar
private:
static Bar mem1;//静态成员可以是自己
Bar* mem2;//指针成员可以是自己
Bar mem3;//错!普通数据成员必须是完全类型
4、const成员函数
只有被声明为const的成员函数才能被一个const类对象调用
const对象不能修改自己,所以不能调用自己的非const函数,只能调用const函数。
在函数的定义和声明处都要加上const关键字,该函数不允许修改类的数据成员,称之为安全函数。
------------------------------------------------------------------------------------------------------------------------------------------------------------------
顺序容器
删除deque中除收尾位置之外的任何元素都会使所有迭代器、引用、指针失效
vector、string中删除点之后位置的迭代器、引用、指针都会失效
c.capacity() 在不会重新分配内存空间的前提下,一共能容纳多少个元素,适用于vector,string
c.reserve(n) 要求分配至少能容纳n个元素的内存空间,适用于vector,string
c.shrink_to_fit() 将capacity减少为与size相同的大小(删除多余的预备内存空间),适用于vector,string,deque
sort(c.begin(), e.end(), 比较函数,用来替代<号)
stable_sort用法相同,但它遇到相等的元素时会维持元素的原有顺序
find_if 对输入序列的每个元素调用给定的谓词,返回第一个使谓词返回非0值的元素,如果不存在这样的元素,返回尾迭代器
算法谓词
有的谓词只接受一个参数,需要用lambda表达式来作谓词
lambda表达式,表示一个可调用的代码单元,可以理解为一个未命名的内联函数,带额外的捕获参数
int min_param = 1;
int max_param = 10;
auto f = [min_param, max_param](int num) -> bool //返回bool的函数,num为参数,min、max作为额外的捕获参数【拷贝】到f中
return num > max_param && num < max_param;
;
min_param = 5;//因为是值捕获,被捕获变量的值在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值
cout << f(5) << std::endl;//会返回5>1&&5<10(即1)
使用引用捕获: auto f2 = [&min_param,&max_param](int num)->bool
此时必须确保被引用的对象在lambda执行的时候是存在的。
值捕获得到的变量一般无法修改,如果要修改,要在参数列表首加上mutable。
引用捕获得到的变量能否修改要依赖于“此引用指向的是一个const类型还是非const类型”
链表list、前向链表forward_list,应优先使用其成员函数版本的算法而不是通用算法,因为链表可以通过改变元素间的链接而不是真的交换它们的值来快速“交换”元素,性能比通用算法要好
------------------------------------------------------------------------------------------------------------------------------------------------------------------
使用智能指针,即使程序块过早结束,智能指针也能在确保内存不再需要时将其释放。
1、shared_ptr:
函数的退出有2种可能,正常处理结束或者发生了异常
void f()
shared_ptr<int> sp(new int(42)); //分配一个新对象
//此处抛出异常,且在f中未被捕获
//在函数结束时,shared_ptr会自动施放内存
void f()
int *ip = new int(42);
//抛出异常
delete ip;//在退出之前释放内存。但是如果发生了异常提前退出,则内存不会被释放。
1、不使用相同的内置指针值初始化(或reset)多个智能指针
2、不delete get()返回的指针
3、不使用get()初始化或reset另一个智能指针
4、如果使用get()返回普通指针,记住当最后一个对应的智能指针销毁后,这个普通指针就变为无效了
5、如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
eg:
connection c = connect(&d);
shared_prt<connection> p(&c, end_connection); //end函数用于代替delete,当p被销毁时,他不会调用delete而是执行end函数
2、unique_ptr:
unique_ptr<T, D> u 使用类型为D的可调用对象来释放它的指针
unique_ptr<T, D> u(d) 用类型为D的对象d来代替delete
eg:
unique_ptr<string> p2(p2.release());//将所有权从p1转向p2,并把p1置为空
unique_ptr<string> p3(new string("Trex"));
p2.reset(p3.release());//释放p2原来指向的内存,将所有权从p3转移给p2
release()会切断指针与对象间的联系,导致丢失指针,因此release的返回值需要管理
p.release();//错误,丢失了指针
auto p = p.release();//正确,可以在稍后delete(p);
------------------------------------------------------------------------------------------------------------------------------------------------------------------
T&& t 右值引用,一个临时值的引用
移动构造函数实现一个对象到另一个对象的元素移动而非拷贝
StrVec::StrVec(StrVec &&s) noexcept//告诉标准库这个移动构造函数不会抛出异常,可以安全使用,否则在重新分配内存过程中,为了异常回滚,它会使用拷贝构造函数而非移动构造函数
:elements(s.elements), first_free(s.first_free), cap(s.cap) //成员初始化器接管s中的资源
s.elements = s.first_free = s.cap = nullptr;//将s的成员转向空指针,因为s是临时值,马上就会自动析构,要断开他与资源的连接
如果一个成员函数能同时提供拷贝和移动的版本,将从中收益。
void push_back(const string& s)//拷贝:绑定到任意类型的string
alloc.construct(first_free++, s);//在指定的元素中构造一个s的副本
void push_back(string&& s)//移动:绑定到string的可修改右值。当实参是一个表达式、字面值等临时值时,移动可以从实参窃取数据,提高性能
alloc.construct(first_free++, std::move(s));//使用string的移动构造函数来构造新元素
------------------------------------------------------------------------------------------------------------------------------------------------------------------
重载
1、成员函数的运算符重载函数,左侧的运算对象绑定到隐式的this指针上,所以总是比运算符的运算数量总数少一个
data1 + data2
operator+(data1,data2)
data1.operator+(data2)
2、不能/不应该重载的运算符:
:: .* . ? : , & && ||
3、如果想提供含有类对象的混合类型表达式,则运算符必须定义成非成员函数
string s = "world";
string s1 = "hi" + s; //如果+是string的成员,则会产生错误,因为"hi"是const char*,一种内置类型,根本没有成员函数
4、输出输入运算符必须是非成员函数
ostream &operator<<(ostream& os, const Sales_data& item)
os << item.isbn() << " " << item.v1 << " " <<item._v2;
return os;
因为无法向标准库的类添加成员函数。IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般被声明为友元。
【输入运算符必须处理输入可能失败的情况。】
5、调用运算符重载
struct absInt
int operator()(int val) const
return val < 0 ? -val : val;
int i = -42;
absInt absObj;
int ui = absObj(i); //将i传递给absObj.operator()
------------------------------------------------------------------------------------------------------------------------------------------------------------------
类型转换
class ClassA
operator int() const return val; //定义了classA向int的转换
ClassA a =4;
a + 3;//会隐式地将a转换为int,在与3加。
如果要求必须显示转换,加上explicit
explicit operator int() const return val;
static_cast<int>(a) + 3;//此处必须显示请求类型转换,否则不通过
如果一个类既定义了运算符重载、又定义了隐式类型转换。则可能遇到二义性问题。
a1 + a2;//到底是先转为int再加,还是直接调用operator+ ?
【派生类向基类的自动类型转换只对引用或指针类型有效,在派生类型和基类类型之间不存在转换。】
在派生类重新声明虚函数的地方最后加入override,告诉编译器正在覆盖一个基类的虚函数,如果找不到名字和参数列表都相符的虚函数可覆盖,编译器会提示报错,避免创建一个新的函数
dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)
将拥有继承关系的对象相互转换,type通常具有虚函数。
如果转换指针失败,返回结果为0(空的指针?)
如果转换引用失败,返回std::bad_cast(定义在typeinfo标准库头文件中)的异常
typeid运算符
if(typeid(*p) == typeid(ClassA))
/.../
当判断的对象是指针,包含虚函数,则需要在运行时根据实际情况求值。否则返回静态类型。
如果是一个空指针,type(*p)将抛出一个bat_typeid的异常。
必须要加*,否则是对指针调用typeid,返回的结果是该指针的静态编译时的类型。
typeid()返回一个typeinfo对象。可以用== 和!= 来比较。
typeinfo::name()可以返回一个C风格字符串用于打印类型的名字
------------------------------------------------------------------------------------------------------------------------------------------------------------------
纯虚函数
double net_price(std::size_t) const = 0;// 声明处写=0表示这是纯虚函数,不需要定义
含有纯虚函数的类是抽象基类,不能直接创建一个抽象基类的对象。派生抽象基类的类如果没有给出所有纯虚函数的定义,则仍是一个抽象基类。
在构造函数和析构函数中,如果要调用虚函数,应当执行与当前类型相对应的虚函数版本
Base::foo();
否则会调用实际运行版本的虚函数,可能导致访问了还没初始化或已经销毁的数据成员。
------------------------------------------------------------------------------------------------------------------------------------------------------------------
模板函数
template <typename T>
int compare(const T& v1, const T& v2)
非类型模板参数
template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char(&p2)[M])
类模板
template <typename T> class Blob
类外定义成员函数
template <typename T>
return_type Blob<T>::member-name(param-list)
模版类型别名
template<typename T> using twin = pair<T,T>;
twin<string> authors;//authors是一个pair<string,string>
模版实参与作用域:
声明:
template <typename T> class BlobPtr
BlobPtr& operator++();//定义前置运算符,在处于一个类模版的作用域时,不用加模板实参。BlobPtr&相当于BlobPtr<T>&
在类外定义:
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++()//此处并不在类的作用域内,直到遇到类名才表示进入类作用域
BlobPtr ret = *this;//此处不用加,因为在函数体内已经进入类的作用域
尾置返回类型与类型转换:
template<typename T>
???& fcn(T beg, T end) //无法确定返回类型
//处理序列后返回一个元素
return *beg;
因为尾置返回类型在参数列表之后,所以可以根据参数推断返回类型
template<typename T>
auto fcn(T beg, T end) -> decltype(*beg)
return *beg;
------------------------------------------------------------------------------------------------------------------------------------------------------------------
内存对齐:
为了提高程序的性能,数据结构,特别是栈,应该尽可能地在自然边界上对齐。
原因在于,为了访问未对其的内存,处理器需要做两次内存访问;然而,对齐的内存访问仅仅需要一次内存访问。
在计算机中,字、双字和四字在自然边界上不需要在内存中对齐(对字、双字和四字来说,自然边界分别是偶数地址,可以被4整除的地址和可以被8整除的地址)。
如果一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,就被认为是未对齐的,从而需要两次总线周期来访问内存。
一个字起始地址是奇数,但却没有跨越字边界,就被认为是对齐的,能够在一个总线周期中被访问。
综上所述,内存对齐可以用一句话来概括——数据项只能存储在地址是数据项大小的整数倍的内存位置上。
#pragma pack(push)//保存对齐状态
#pragma pack(1)
class TA
int i;
char ch;
;
#pragma pack(pop)//恢复对齐状态
printf("%d", sizeof(class TA));//本来占用8字节,使用pragma pack(1) 以后只占5字节
------------------------------------------------------------------------------------------------------------------------------------------------------------------
default_random_engine e;
uniform_int_distribution<unsigned> u(0,9);
int randomNum = u(e);
每次生成的数字都是一样的,必须要把e和u声明为static,这样他们不会从头开始而是跟着上一次的随机数生成后面的输。
但是每次运行的结果仍然一样,需要在e加入参数seed,e(time(0)),根据当前的秒数来生成
------------------------------------------------------------------------------------------------------------------------------------------------------------------
命名空间
#include不好放在命名空间内部,这样会把头文件中包含的其他命名空间,如std,定义到自定义的命名空间中
在命名空间内声明,不能在无关的命名空间内定义,但是可以在父级命名空间中定义。
如果原始模版在某个命名空间中,它的特例化的声明也需要在命名空间中。定义可以在命名空间外
全局命名空间没有名字,::member_name 表示一个全局命名空间中的成员
未命名的命名空间,只在特定文件内部生效,可以直接访问,没有自己的名字。
未命名空间中定义的变量拥有静态生命周期,第一次使用前创建,直到程序结束才销毁。
int i; //全局声明
namespace
int i;
i = 10;//二义性,因为两个都可以直接访问
using指令(directive)
using namespace std;//减少使用,它会把该命名空间提升到外层空间,同名的函数被添加到重载集合中。这容易发生命名空间污染
using声明(declaration)
using std::string;//声明一个名字
using NS::print(int);//错误的,不能指定形参列表,只能写函数名,将该函数的所有版本都引入到当前作用域。
查找命名空间的例外:
std::cin >> s;//输入一个值给字符串
operator>>(std::cin, s); //等价于这种写法,operator>>前不用加std::,因为给函数传递参数时,会额外查找实参类所属的命名空间
此时自动找到了istream和string所在的std命名空间,调用std的string输入运算符。
------------------------------------------------------------------------------------------------------------------------------------------------------------------
多重继承和虚继承
Endangerd
Bear |
/ \\ |
ZooAnimal [Panda]
\\ /
Raccon
class Racoon : public virtual ZooAnimal/*...*/
class Bear : virtual public Bear/*...*/
class Panda : public Bear, public Raccon, public Endangerd/*...*/
因为是虚继承,ZooAnimal的成员只会存在唯一的一份在Panda里,如果是正常继承会有多份副本
当创建一个Panda对象时,需要在Panda的构造函数初始值列表中构造虚基类ZooAnimal(正常情况是不能跨级写构造函数的)
接下来构造Bear、Raccon、Endangered、Panda的部分。
如果Panda没有显式地初始化ZooAnimal基类,会调用他的默认构造函数,如果没有默认构造函数,代码会出错。
虚基类的构造函数 都是在 继承或间接继承它的类中 构造的,即时是多层继承关系,也需要每个类自己实现虚基类的构造函数
------------------------------------------------------------------------------------------------------------------------------------------------------------------
枚举
限定作用域:
enum class colorred,yellow,green;//默认为int
color i = color::red;
不限定作用域
enum colorred,yellow,green;//一个足够大的类型
color i = red;//因为不用加访问限定符,所以不同枚举中的名字也不能重复
手动指定枚举的值,可以用数字或者常量表达式(constexp)
指定枚举的大小://
enum intValues : usigned long long charTyp = 255, shortTyp = 65535
数字不可以赋给枚举类,但是枚举类可以赋给数字,此时枚举会提升到int或更大的整型
------------------------------------------------------------------------------------------------------------------------------------------------------------------
数据成员指针
const string Screen::*pdata = &Screen::contents;//或auto *pdata
Screen myScreen; Screen* pMyScreen;
string s = myScreen.*pdata;
auto s = pMyScreen->*pdata;
成员函数指针
using Action = Screen& (Screen::*)();//Action是Screen类的一个不接受参数、返回Screen引用的函数的指针
Action fPointer = &Screen::forward;
this->*fPointer();//this是一个Screen类的对象的指针
------------------------------------------------------------------------------------------------------------------------------------------------------------------
嵌套类声明
class TextQuery
public:
class QueryResult;//嵌套类声明,稍后定义
嵌套类在外层类之外定义:
class TextQuery::QueryResult
名字查找规则
嵌套类本身是一个作用域,同时还会查找嵌套类外层的作用域。(嵌套类可以直接使用外层类作用域的东西不需要前缀)
以上是关于c++11笔记的主要内容,如果未能解决你的问题,请参考以下文章