c++ primer复习(3)那些太容易被忽略的细节(持续更新)

1.std::endl 有一个作用是来冲洗buffer

2.声明语句时理解方式typedef

typedef char* pstring;
//这两看似一样,但是实际不同
const pstring cstr = 0;	//char*为基本数据类型
const char* cstr = 0;   //char 为基本数据类型,*变成声明

3. const和constexpr

常量表达式:
constexpr 指定的一定是常量表达式(编译期可求值);
constexpr受到的限制,constexpr指针必须是nullptr或0,或者是存储于某个固定地址中的对象.

const和constexpr的区别:
对象常量性可分为两种: 物理常量性(每个bit都不可能改变)和逻辑常量性(对象的表现保持不变).
C++中采用的是物理常量性.
例如:

class ME{
public:
	int *p;
}

int a = 5, b = 6;
const ME tmpME = {&a};		//编译器创建的构造函数
tmpME.p = &b;				//编译器报错:tmpME.p指针所指地址不能变
*(tmpME.p) = 10;			//不报错: tmpME.p的地址没变,地址保存的"值"变了

constexpr值可用于enum,switch,数组长度等场合,有很强的约束更安全,编译器对constexpr的优化很大,可将用到constexpr表达式的地方直接替换成最终结果.

3.auto声明

多条声明语句类型必须一致

auto i=0,*p=&i;

4.std::size_t

是一种std::string里内置的变量类型,是无符号的,所以要小心跟负值的int比较

5.std::string 字符串相加遵守原则:

两相加的对象中至少一个必须是string

6.std::string 类的输入运算符和getline函数分别是如何处理空白字符的

输入运算符 自动忽略空格,制表符(/t),换行符(/n), getline保留输入时的空白符.

7.include头文件

处理的一组标准函数.用于字符串的处理和判断
但是输入时 int _C ,用于检测单个字符类型的库吧.
c++ primer复习(3)那些太容易被忽略的细节(持续更新)

8.decltype(things)

用于检测things的对象类型
可用于一些类型模糊的对象提取他们的对象类型

string str("hello");
decltype(str) punct_cnt = 0;			//类型是:std::size_t

9.std::string的下标合法性

//合法不合法跟运行成不成功没啥关系
//下标合法性,就是说0~string.size()之间的下标为合法(c++primer说的),不合法即下标超过size()
//所以string是空时 0~0是合法的下标
//这句能运行成功
//string为空,则s[0]的结果将是未定义的.s[1]就直接报错

std::cout <<"输出空字符串中的tt[0]:"<< tt[0] << std::endl;

10.初始化列表在vector中应用时

vector<string> v5{"hi","me"};		//列表初始化,v5有2个元素
vector<string> v6("hi");			//错误:不能使用字符串字面值构建vector对象
vector<string> v7{10};				//v7有10个默认的初始化元素
vector<string> v8(10,"hi");			//v8有10个值为"hi"的元素

11.遍历vector时顺便修改值

std::vector<int> v{ 1,2,3,4,5,6,7,8,9 };
for (auto &i : v)       //可以修改v的值
	i *= i;
for (auto i : v)
	std::cout << i << " ";
std::cout << std::endl;

vector对象的下标只能用于访问已存在的元素,而不能用于添加元素;
下标形式访问不存在的元素不会被编译器发现,而是在运行时产生一个不可预知的值,即缓冲区溢出错误(buffer overflow);

12.简化vector访问元素的方法->箭头运算符

//字符串向量中对象为空时停止循环,关键点位it->empty() 检测字符串是否为空
//it->empty() 访问 string对象,比起(*it).empty()更便捷
std::vector<std::string> text = {"wo","cao","li","","made","bi","zui"};
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it)
	cout << *it << endl;

13.vector的一个限制

:不能在for循环中向vector对象添加元素,但是可以删除元素.

14.迭代器位置类型名为 difference_type 的带符号整型数,因为距离可正可负

15.数组由内向外,由右向左解读

int *(&array)[10] = ptrs;		//arry是数组的引用,该数组含有10个指针

首先知道arry是个引用,然后观察右边知道arry引用的对象时一个大小为10的数字,最后观察左边知道,数组的元素类型是指向int的指针.

int (*parray)[10] = &arr;  		//parray指向一个含有10个整数的数组

首先圆括号起来的部分意味着parray是个指针,接下来观察右边可知oarray是个指向大小为10的数组指针
最后看左边可知数组元素时int型.

int *ptrs[10];					//ptrs是含有10个整型指针的数组

从右向左,首先可知定义的是一个大小为10的数组,他的名字是ptrs,然后再看类型是(int*)整型指针

char st[11] = "fundamental" ;   //错误,没有空间放空字符串"\0"

16.数组与vector的区别

数组运行时性能较好,但是不能像vector随意增加元素,损失了灵活性

//c++ primer 第五版 练习题 3.28 

//问下列数组中元素的"值"是什么?
#include <iostream>
using namespace std;

string sa[10];
int ia[10];						//没明白为什么这个全局变量是会自动初始化,后续再找找补充(以补充)
char a[5];						//也会被初始化为空字符串
int main(){
	string sa2[10];
	int ia2[10];			
}

//sa,sa2里面的值都10个空字符串
//ia里面是10个0值
//ia2里面是10个-858993460(垃圾值:未初始化的值)
//可以看到c的值为-858993460这个值即为0xcccccccc的十进制表示。
//原因是:未初始化的栈区编译器默认(在vs2017下)都按照cc去填充了。另外补充,在gcc编译器则是按照0填充的

(补充如下)
默认初始化规则:
定义基本数据类型变量的同时可以指定初始值,如果未指定会默认初始化
1.栈中的变量和堆中的变量会保有不确定值

2.全局变量和静态变量(包含局部静态变量)会初始化为零

静态和全局变量的初始化
未初始化的和初始化为零的静态/全局变量编译器是同样对待的,把它们存储在进程的BSS段(这是全零的一段内存空间)中.所以它们会被"默认初始化"为零

成员变量的初始化
成员变量分为成员对象和内置类型成员,其中成员对象总是会被初始化. 而我们要做的就是在构造函数中初始化其中的内置类型成员.
内置类型的成员变量的"默认初始化"行位取决于所在对象的存储类型,而存储类型对应的默认初始化规则时不变的.所以为了避免不确定的初值,通常会在构造函数中初始化所有内置类型的成员.

封闭类嵌套成员的初始化
同样还是只关注于基本数据类型的成员.取决于当前封闭类对象的存储类型,而存储类型对应默认初始化规则仍然是不变的

17.c++ 11标准引入的两个名为 begin和end的函数(头文件iterator)

// 头文件iterator
int a[] = {0,1,2,3,4};
int *beg = begin(a);		//指向a的首元素
int *last = end(a);			//指向a的尾元素下一位置指针,注意尾指针不能解引用和递增

18.给指针加上一个整数,得到的新指针仍需指向同一数组的其他元素,或者指向同一数组的尾元素的下一位置:

constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *p = arr + sz;		//使用警告: 不要解引用
int *p2 = arr + 10;  	//错误:p2的值未定义
//编译器发现不了,程序运行到这里就会指针越界崩溃

int *b = arr, *e = arr + sz;
while(b<e){++b;}
//如果两个指针分别指向不相关的对象,就不能比较

19.c风格字符串函数注意:

strlen,strcmp(p1,p2),strcat(p1,p2),strcpy(p1,p2)
此类函数的指针必须以空字符串为结束的数组.

20.下面程序运行结果是什么?

void test_337()
{
    const char ca[] = { 'h','e','l','l','o' };
    const char* cp = ca;        //const 表示所指物为常量
    while (*cp) {
        cout << *cp << endl;
        ++cp;
    }
}

// 因为ca是C风格字符串,字符串结尾必须带'0'
// 字符串ca在内存中的位置不断向前寻找直到遇到空字符串才停下来

21.两个指针相加为何没有意义

因为相加后的指针指向的值,跟相加的两个指针没什么关系,而且指向的值也不是我们想要的结果.

22.能用数组初始化vector,通过指定指针首尾

//需要头文件#include<vector>
int int_arr = {1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));		//begin和end需要头文件#include<iterator>
//也可以部分赋值
vector<int> subvec(int_arr + 1,int_arr +4);			//赋值int_arr[1]~[3]
// 列表初始化vector
vector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

不允许使用一个数组为另一个内置类型的数组赋初值
不允许使用vector初始化数组

上一篇:《C++ Primer 中文版(第 5 版)》1.2


下一篇:《C Primer Plus(第6版)中文版》 第6章 C控制语句:循环