C++ Primer第三章 心得笔记

之前的一章我本末倒置了,我看了一个大佬的此书笔记整理得很详细 很得体。我也想按照他的这种方法 在我学习和敲代码的时候进行记录,但是我发现为了记笔记而记笔记 这种方法使我很累。违背了记录分享交流的初衷。所以也希望才开始的同学,能吸取我的教训 少走一点弯路。(我个人认为 不应该把所有的知识点罗列出来,应该在保持状态的同时 记录重要的点 问题,能解决的直接解决,不能解决的先记录 搁置一边,继续往下学习,不要浪费了进入的状态)

我觉得所谓心得笔记 应该是记录自己所遭遇的问题,和觉得重要的点,然后再把解决问题的思路和过程分享出来。而不是为了表里来记笔记 这样再未来的复习的时候,复习到的就没有那么好的复习效果。

在处理string 类对象时,如使用下标运算符等方法访问string类对象的字符时,应首先确认是否为空,其中一个方法为使用empty()函数确认string类对象是否为空,若为空则返回真,不为空则返回假,这里需要注意表达式取反。

string对象的几种初始化方式

	string str = "Hello Word!";									//拷贝初始化
	string str1 = ("Hello Word!");								//暂且不知道属于哪种初始化
	string str2 = { "Hello Word!" };							//暂且不知道属于哪种初始化
	string str3("Hello Word!");									//直接初始化
	string str4{ "Hello Word!" };								//列表初始化
	string str5{'H','e','l','l','o',' ','W','o','r','d','!'};	//列表初始化
	string str6(10,'H')											//直接初始化
    /*
	*	输出结果
	*	Hello Word!
	*	Hello Word!
	*	Hello Word!
	*	Hello Word!
	*	Hello Word!
	*	Hello Word!
	*	HHHHHHHHHH
	*/

for循环原来还可以分成多行来写(但是每一行的结尾必须以分号结尾),刚才看书的时候楞是没反应过来。

#include <iostream>
#include <cctype>

int main() {
	using std::string;
	using std::cout;
	using std::endl;
	using std::cin;

	string a("Hello Word!");

	for (decltype(a.size()) index = 0;				//表达式1
		index != a.size() && !isspace(a[index]);	//表达式2
		++index)									//表达式3
		a[index] = toupper(a[index]);
	cout << a << endl;

	//输出结果 HELLO Word!
	return 0;
}

直接初始化 和 拷贝初始化
“=” 拷贝初始化
“()” 直接初始化

vector是模版(理解为 容器),而非类型。由vector生成的类型必须包含vector类型中元素的类型。如vector。

为什么会将string对象的下标类型设置为 string::size_type,因为我们得确保下表的范围不小于0。所以这里统一设置为这个string::size_type这个无符号类型,就可以确保下标不会小于0。一旦超出这个范围就会发生无法预知的后果。

vector容器的初始化

	vector<int> it(10, 5);				     //容器里有10个整形元素 值都为5
	vector<int> it1(10);                     //容器里有10个整形元素 值都为0
	vector<int> it2{ 10 };                   //一个元素 值为10
	vector<int> int3{ 10,5 };                //两个元素 分别为10和5
	
	vector<string> str(10);                  //十个元素 默认初始化
	vector<string> str1{ 10 };               //十个元素 默认初始化
	vector<string> svec(10, "null");
	vector<string> str2{ 3,"hi"};            //三个元素 值为hi
	vector<string> str3{"Hellow","Word","!"};//三个元素 值分别为Hello Word !

关于迭代器。有两种类型
一种是iterator(可读可写),一种是const_iterator(只读)。

begin和end的返回的具体类型由对象是否是常量而决定。

vector<string> str{"Hello Word!"};
vector<string>::iterator str_iterator = str.begin();
//返回类型为iterator
//等价 auto str_iterator = str.begin(); 

const vector<string> str1{"Hello Word!"};
vector<string>::const_iterator str1_iterator = str1.begin();
//返回类型为const_iterator
//等价 auto str_iterator = str1.begin(); 

而另外两种函数cbegin、cend,无论对象是否为常量,它的返回类型都为const_iterator
无法通过类型为const_iterator的迭代器修改容器迭代器指向元素的值

vector<string> str{"Hello Word!"};
vector<string>::const_iterator = str.cbegin();

谨记 但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
谨记 范围for语句体内不应改变其所遍历序列的大小

从cin读入一组词并把他们存入一个vector对象,然后把所有词都改为大写形式。输出改变后的结果,每个词占一行。

#include <iostream>
#include <cctype>
#include <typeinfo>
#include <vector>

int main() {
	using std::string;
	using std::cout;
	using std::endl;
	using std::cin;
	using std::vector;

	vector<string> v1;
	string temp;
	//通过循环从标准输入中读取单词
	while (cin >> temp) 
		v1.push_back(temp);

	//第一个for循环遍历容器的string元素
	for (auto v1_iterator = v1.begin();
		v1_iterator != v1.end(); ++v1_iterator) {
	//第二个for循环遍历string元素的每个字符 同时把单词变为大写
		for (decltype((*v1_iterator).size())index = 0;
			index != (*v1_iterator).size(); ++index) {
			(*v1_iterator)[index] = toupper((*v1_iterator)[index]);
		}
	}

	for (auto c : v1) {
		cout << c << endl;
	}
	return 0;
}

关键概念:泛型编程
为什么使用 != 而不是使用<或者<=,因为所有标准库容器的迭代器都定义了==和!=,但是大多都没有定义<运算符。所以我们只要养成使用迭代器和 != 的习惯,就不用太在意到底是使用哪种容器类型。

字符串字面值的类型实际上是由常量字符构成的数组,编译器在每个字符串的结尾处添加一个空字符‘\0’。因此,字符串值面值的实际长度比它的内容多1。

字符数组的特性
我们可以用字符串字面值,对此类数组初始化,当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符。这个空字符也会像其他字符串的其他字符一样被拷贝到字符数组中去。

char str[]=“Hello Word!”;
char str1[11]=“Hello Word!”;
//初始值的总数量不应该超出指定的大小

不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。

想要理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读。

在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。
在大多数表达式中,使用数组类型的对象其实上是使用一个指向该数组首元素的指针。

int a[10]={}; //{}是列表初始化 10个0
int *p=&a[0];
//和下面这条代码 等价
int *p1=a;

指针也是迭代器

通过数组名字或者数组中首元素的地址都能获得只想首元素的指针。
而尾后指针(尾元素的下一位置)就要用到数组的另一个特殊性质。尾后指针不指向具体的元素。因此,不能对尾后指针执行解引用或则递增的操作。(递减可以)

比较两个数组

    int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int b[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int b[10] = { 1,3,5,7,9,11,13,15,17,19};

	auto a_num = size(a);
	auto b_sum = size(b);

	if (a_num == b_sum){//数组的元素数量相等  循环内部对元素进行挨个比较 
		for (size_t index = 0; index != a_num; ++index) {
			if (a[index] != b[index]) {
				cout << "值不相等" << endl;
				exit(0);
			}
		}
		cout << "相等" << endl;
	}
	else{
		cout << "数量不相等" << endl;
	}
	return 0;

比较vector对象是否相等

	vector<int> a{10,8,3};
	vector<int> b{ 10,8,3 };
	size_t index = 0;
	if(a.size()==b.size()){
		while (index != a.size()) {
			if (a[index] != b[index]) {
				cout << "值不相等" << endl;
				exit(0);
			}
			++index;
		}
		cout << "相等" << endl;
	}
	else {
		cout << "数量不相等" << endl;
	}
	return 0;

分析以下代码段


	const char ca[] = { 'h','e','l','l','o' };
	//const char ca[] = { 'h','e','l','l','o','\0' }; 正确格式
	const char* cp = ca;
	while (*cp) {
		cout << *cp << endl;
		++cp;
	}
	return 0;
//输出:hello烫烫烫恬滋掣??4

ca应该是想表达c风格字符串 但是尾元素必须是空字符 ‘\0’。但是由于数组尾元素未设置为空字符,所以循环未按照逻辑(在循环尾元素处结束)。地址一直递增,知道解引用时遇到内存中的空字符。

多维数组
通常所说的多维数组其实是数组的数组,按照由内而外的阅读顺序读此类定义有助于更好地理解真实含义。
例如

int a[3][4]={
	{1,3,8,1},
	{2,3,4,6},
	{1,2,8,4}
};

我们定义的数组名字为a,a是一个包含三个元素的数组,接着往右边发现这三个元素也有自己的维度,a的元素本身就是一个包含四个元素的数组。
对于二维数组,我们通常把第一个维度叫做行,第二个维度叫做列。

constexpr 和 const的区别
在书里经常能看到,一会使用constexpr,一会使用const。具体有什么差异呢?我百度了一下,做了一点肤浅的总结。
constxepr是编译期常量(具体什么是编译期常量 我也没理解明白)。
const就没有区分编译期常量和运行期常量。

在敲代码时,语义在常量就用constexpr。语义在只读就用const。

而检测constexpr函数是否产生编译时期值的方法很简单,就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,即可检测你所写的函数是否真的产生编译期常值了。

int main()
{
    using namespace std;
    constexpr int a_row = 3, a_lie = 4;
    int a[a_row][a_lie];
    size_t index = 0;
    for(auto &row:a)
        for (auto& lie : row)
        {
            lie = index;
            ++index;

        }

    for (auto& row : a)
        for (auto& lie : row)
        {
            cout << lie << endl;

        }
    return 0;
}

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

指针和多维数组
当程序使用多维数组的名字时,也会自动将其转换为指向数组首元素的指针。(多维数组是数组的数组),所以这个指针指向的元素也是一个数组。

int main()
{
    using namespace std;
    int a[4][3] = {
        {13,4,2},
        {1,57,1},
        {15,48,15},
        {48,79,16}
    };

	//x是一个指针 指向数组的第一行的指针
    for (auto x = begin(a); x != end(a); x++) {
    //*x是一个数组,而用这个数组赋值给y 实际上是把这个数组的第一个元素的地址赋给了y
    //end(*x)实际上返回的是数组a第一个维度的尾后地址(尾后是指末尾元素,尾后地址则是指末尾元素的后面的地址)
    //尾后地址不能解除引用,也不能用作算数运算 因为没有实际意义,它不指向任何一个元素,只做参考
        for (auto y = begin(*x); y != end(*x); y++) {
            cout << *y << " ";
        }
        //理解a  x  *x  y  *y的含义
        cout << endl;
   }
上一篇:温习C++ Primer【一】


下一篇:《C++ Primer》笔记 第18章 用于大型程序的工具