精读C++ primer

从今以后,精读primer遇到漏洞或者新知识在这里记录下来~

1.C++11后,可以进行列表初始化赋值例如:

int a=0; int a(10); int a={0}; int a{0};后2中属于列表初始化,但是遇到内置类型的变量,不能使用列表初始化,不然会报错

比如long double id=3.1415926536;   int a{id};//报错


2.double d=3.14,  int &a=d;是错误的,因为引用类型的两边不对称,但是如果改成 double d=3.14, const int &a=d;就不会报错,因为会相当于生成一个临时变量const int temp=d; 然后const int &a=temp;就不会报错,更具体的原因primer上有


3.int *p=0是对的,代表p被初始化,但没有指向任何对象,因为C++规定,常量整形值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void*,指向任意对象的指针能转换成const void*,int *p=1, int &p=0;都是错的,const int &p=0是对的,因为2边都是常量,常量引用可以绑定字面值常量,const 对象必须初始化,因为创建后值不能被改变



4.C++里面的const 对象默认仅在文件内有效



5.int  a=1;  int *p1=&a;   const int  b=2;   const  int *p2=&b;

那么p1=p2是非法的,因为p1是普通指针,指向的对象可以是任意值, p2是指向常量的指针,令p1指向p2所指向的内容,有可能错误的去改变常量的值


6.使用auto也能在一条语句中声明多个变量.因为一条声明语句只能有一个基本数据类型.例如

auto i=0, *p=&i; //正确

auto i=0,  p=3.14;//错误,i与p类型不一样

其中包括不能一个变量是int,另外一个是const int

比如:int a=1; const int b=2;

auto &i=a,*p=&b;//错误,a的类型是int ,b的类型是const int,所以错误


7.auto会忽略掉顶层const,但是底层const会保存下来例如:

int a=1; const int b= 2;

auto c=b;//c是一个整形而不是常量整形

auto c=&a;//c是一个整形指针

auto c=&b;//c是一个指向整形常量的指针(对常量对象取地址是一种底层const)

如果希望推断出的auto类型是一个顶层const,需要明确指出:

int a=1;

const auto i =a; //i是const int 类型


8.使用decltype注意一点,如果获得的是引用类型,那么必须初始化例:

const int a=1, &b=a;

decltype(b) c;//错误,c是一个引用,必须初始化

int a=1 ,*p=&i;//这里的decltype(*p)有点特殊,具体原因见primer63

decltype(*p) b;//错误 ,b是int &,必须初始化

//decltype的表达式如果是加上了括号的变量,结果将是引用

int i=1;

decltype ((i)) d; //错误,d是int&,必须初始化

decltype (i) e;//正确,e是一个(未初始化的)int

如果想使decltype(x)引用类型不初始化,那么可用decltype(x+0)转化成一个int,而非int&




9.因为历史原因和为了与C兼容,C++语言中的字符串字面值不是标准库string的对象即 "hello" 和string s="hello"不是同一个类型

而且,加法运算符两侧至少有一个是string对象

string a="hello";

string b=a+","+"world;//正确,a+","先结合形成string对象,再和world形成 string对象

string b= "hello"+","+a;//错误, "hello"+","是两个字符串字面值,不能相加赋值给string 对象


10.int arr[10];

int  (*p)[10]=&arr; //&arr是一个数组指针,指向int [10]的数组,所以要用数组指针接纳

int  (&p)[10]=arr; //p引用一个含有10个整数的数组

而且修饰符的数量没有限制

int  *(&p)[10]=arr;



11.比较C风格字符串时,不能直接比较,因为比较的指针而不是字符串本身,但是可能2个指针

指针向的并非同一对象,所以将得到未定义的错误,需要调用strcmp函数,C++字符串则可以直接比较

谨记:2个指针的减法有意义,2个指针的加法没意义



12.可以使用数组来初始化vector对象.要实现这一目的,拷贝区域的首地址和尾后地址就可以了.

int arr[] = { 1,2,3,4,5,6 };

vector<int>v(begin(arr),end(arr));

当然也初始化vector对象也可能是数组的一部分.

int arr[] = { 1,2,3,4,5,6 };

vector<int>v(arr+1,arr+4);



13.要使用范围for语句处理***数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型

具体见primer114



14 .int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };

for (auto p = a; p !=a+3; p++)

{

for (auto q = *p; q != *p + 4; ++q)

{

cout << *q << " ";

}

cout << endl;

}

也能遍历数组,p是一个数组指针,指向了含有4个整数的数组,q是指向一个整数数组的首元素.

那么*q的含义就和int a[]={3,4,5},int b=*a;这个*a的含义一样,属于解引用,所以能遍历数组



15.size_t是cstddef头文件中定义的一个与机器实现有关的无符号整形类型,它的空间足够大,能够表示任意数组的大小

size_type是string和vector定义的类型的名字,能存放下任意string对象或vector对象的大小.在标准库中,size_type被定义无符号类型



16.int a[3][4] = { 3,4,5,6,7,8,9,10,11,12,13,14 };

int(&p)[4] = a[0];//把p定义成一个含有4个整数的数组的引用,然后将其绑定到a的第1行




17.const_cast可以改变对象底层const(只有这个可以),但是有可能产生未定义的行为.如果对象本身是一个非常量,强制转换后获得写权限是合法的,

但是如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果.例如:

这样是没有问题的:

const int *p2;   int a = 2;

p2 = &a;

int *p = const_cast<int*>(p2);

*p = 3;

cout << a;

输出是3.但是把int a改成 const int a=2;就是未定义了


18.initializer_list是可变参数模板,是一直标准库模型,例如:

void error_msg(initializer_list<string> il)

{

for (auto beg = il.begin(); beg!=il.end(); beg++)

{

cout << *beg << " ";

}

cout << endl;

}


int main()

{

int a = 0;

if (a == 0)

{

error_msg({ "a","b","c" });

}

else

{

error_msg({ "a","b"});

}

return 0;

}



19.函数调用返回类型是引用的话得到是左值,其他返回类型得到右值.我们可以像使用其他左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值:(注意是非常量)



20.C++11新标准规定,函数可以返回花括号包围的值的列表,此时的列表页用来对表示函数返回的临时量进行初始化



21.,返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果,记住,一般都会拷贝一个副本返回,但是如果返回的是引用的话,那么就不会拷贝副本,返回的是一个别名.什么情况下返回的引用会无效呢? 如果引用所引用的是函数开始之前就已经存在的对象,那么返回的引用是有效的,如果是引用的函数的局部变量,函数结束时候就失效了,此时返回的引用无效.当不希望返回的对象被修改时,返回对常量的引用



22.C++新语法糖:使用尾置类型返回

//返回指向数组的指针

auto func1(int arr[][3], int n) -> int(*)[3] { return &arr[n]; }

这段代码非常符合尾置返回类型设计的用途,按照以往的方式我们可能需要:

C++

int (* func1(int arr[][3], int n))[3] { return &arr[n]; }


23.关于函数重载有几个要注意的地方:
1.不能通过返回值不相同来判断两个函数不一样
例子:int fun(){ return 1;} void  fun(){};//错误
2.不能通过忽略形参名字来判断是不是一个新函数.
例子:int fun(int a){return 1;}  int fun(int){return 1;}//仅仅忽略的参数名字,错误
3.这个也是最容易出错的,顶层const无法重载一个函数使它与原函数形成函数重载,
例子:
int func(int)
{
cout << "a";
return 0;
}
int func(const int a)//错误
{
cout << "b";
return 0;
}
但是如果是底层const就可以,就是通过区分其指向的是常量对象还是非常量对象
例子:
int func(int *a)
{
cout << "a";
return 0;
}
int func(const int* a)//正确,可以把*都换成引用
{
cout << "b";
return 0;
}


24.const_cast在重载函数中最有用,具体原因见primer209


25.一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值


26.函数指针指向的函数,它的返回类型和参数必须相匹配


27.定义在类内部的函数是隐式的inline函数


28.不同于其他成员函数,构造函数不能被声明成const的.当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其"常量"属性.因此,构造函数在const对象的构造过程中可以向其写值


29.通常如果一系列操作在同一个对象上执行,那么我们一般会返回*this,以免生成副本,修改不成功


30.一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用


31.通过区分该成员函数是不是const函数,我们可以对其进行重载.(但是也有一个需要注意的地方,参数是const需要区别两个情况,看是不是重载函数)


32.static成员函数为什么不能是const?
"修饰符不可用于静态成员函数"
看书的时候认真点(前提是选对书),这问题就应该直到答案,
或者多动动脑,思考一下应该也能知道,说说我的观点:

书上至少应该说过,const的这种用法(indicate 不会修改当前对象的内容),
只能用于类的一般成员函数,而不能用于静态成员函数。

静态成员函数有什么不同?根本上说,静态成员函数里面没有this指针,
就是说,相当于一个定义于该类内部的普通(非成员)函数,
这也是为什么静态成员函数不能访问成员变量的原因,
所以,静态成员函数根本不存才“改变this指向的内容”这个概念,
结论(at last):const这个修饰符,用于静态成员函数没有意义。


33.我们一般使用作用域运算符直接访问静态成员,虽然静态成员不属于类的某个对象,但是我们仍然可以使用类的对象,引用或者指针来访问静态对象(不过是多此一举而已),成员函数可以不通过作用域直接访问静态成员。



34.静态成员能用于某些场景,而普通成员不能,举个例子,静态数据成员可以是不完全类型。特别的,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用:
class  Bar{
public:
//........
pritvate:
static Bar mem1;//right
Bar  *mem2;       //right
Bar   mem3;       //wrong
}
静态成员和普通成员的另外一个区别是我们可以使用静态成员作为默认实参,非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误



35.不要保存end返回的迭代器,见primer316页



36.必须记住,一个map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字成员的值



37.set的迭代器是const的,虽然set类型同时定义了iterator和const_iterator类型,但两种类型都只允许只读访问set中的元素。与不能改变一个map元素的关键字一样,一个set中的关键字也是const的。可以用一个set迭代器来读取元素的值,但不能修改


38.非类型模板参数的模板实参必须是常量表达式,编写泛型代码的两个重要原则:1.模板中的函数参数是const的引用,函数体的条件判断仅使用<比较运算


39.为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非类模板代码不同,模板的头文件通常既包括声明也包括定义,所以说模板过多,也会造成头文件很大


40.当使用STL时候,传递参数一般不使用非引用形参,可以使用引用形参,不过一般都会传递迭代器进入运算


41.当你使用C++11新增的范围for语句,比如
for (auto &row:a)
    for(auto &col :row)
最好都使用引用,因为编译器初始化row时会自动将这些数组形式的元素转换成指向该数组内首元素的指针.这样得到的row的类型就是int *,显然内层循环就不合法了,编译器将试图在一个Int *内遍历
全部评论
c++的const好像是在底层是read only,可以退化的。好久没看,有些生疏了。
点赞 回复 分享
发布于 2016-11-26 22:56

相关推荐

04-02 22:40
已编辑
电子科技大学 后端
谢谢大家啦!!!
坚定的芭乐反对画饼_许愿Offer版:有鹅选鹅,没鹅延毕
点赞 评论 收藏
分享
剑桥断刀:找啥工作,牛客找个比如大厂软开或者随便啥的高薪牛马,大把没碰过妹子的技术仔,狠狠拿捏爆金币
点赞 评论 收藏
分享
评论
5
18
分享

创作者周榜

更多
牛客网
牛客企业服务