C++Primer第5版学习笔记(二)

时间:2022-06-26 23:52:42

C++Primer第5版学习笔记(二)

第三章的重难点内容
 
      这篇笔记记录了我在学习C++常用基本语法的学习过程,基本只记录一些重难点,对概念的描述不是一开始就详尽和准确的,而是层层深入的。第三章主要讲这么五个概念:

      1.using声明,我知道挺多同学写代码练手都要在源文件前几句直接加using namespace std;然而using语句并不是什么情况都这么使用的,稍后我们将会看到详细的用法。

 2.标准库类型string,和C味的字符数组有区别的string,到底是怎么个构造,这章将会讲述。

 3.标准库类型vector,vector和数组区别很大,这里将会提到,并引入一个“容器”的重要概念。

4.迭代器,迭代器用来代替下标这种传统方式访问容器或一些支持迭代器的类型。

      5.数组和多维数组,经典概念。

      下面是这一章的知识点:
      知识点1:P75,3.1,头文件和using语句(英文版84页)
      尽管我们可以在各种文件里都使用using namespace std;或者using std::endl;这种语句,但是,在头文件包含命名空间可能产生各种意外。因此,头文件不应包含using声明。cocos2dx引擎3.2的版本就在头文件也使用了using namespace的语句,在现在3.10版本已经在头文件里用命名空间标识符来代替以前的做法。可见不在头文件中使用using语句是很标准的用法。(这里的using 语句指的只是使用命名空间作用域的using语句,不包括using SI=int这种别名声明语句。)
 
      知识点2:P76,3.2.1,定义和初始化string对象(英文版85页)

      以下几种初始化语句被string支持:

string s1;//创建了一个空的字符串,对象名为s1,类型为string类型。

string s2(s1);//是s2的值与s1的值相等。

string s2=s1;//同上一句,拷贝初始化。

string s3("value");//直接用字符串字面值初始化string类型的对象。

string s3="value";//字符串字面值转化为string类型变量并赋值给string。

string s4(n,'c');//直接初始化string,操作后s4拥有10个字符,每个字符的值都是'c'。

最后,其实string s5={"value"}和string s3="value"一样,也是合法的。不过大括号初始化是严格检测匹配的,比如int a={3.5};就是错误的。

 
      知识点3:P77,3.2.2,string支持的操作(英文版86页)
      1.输入流中获取字符串 首先要强调是cin>>string的操作,这种操作就是从输入流中读字符串,值得注意的是这个过程会忽略掉开头输入的各种空白(我们说空白时是在说  空格,换行符,制表符),读取输入流直到遇到字符后的第一个空白为止,另一种getline(cin,string);的操作则可以读一行,也就是读入输入流的数据(包括空格,制表符),直到遇到换行符为止,这里输入流中的换行符本身已经被读过了,但是字符串里不保存这个换行符。下次再从输入流里读什么数据至少也要从这个换行符后面对输入流进行操作了。
      2.string::type_size 为了更抽象,脱离机器特性,调用每个string对象的size成员函数,返回值都是一个string::type_size类型,这个类型拥有无符号整形数的一些性质。在string对下标的支持中,[ ]中的数字也会被转换为string::type_size类型。这里要强调的是string::type_size是一个无符号类型。使用这个类型和int型这种有符号的类型一起进行计算可能出现一些错误。

3.string对象与字符串字面值相加  字符串字面值是字符数组类型,字符串字面值和string类型的对象在一起计算时会被自动转换为string类型。

4.其他支持的操作:包括下标运算符[ ]、重载的+、==、!=、<、>、<=、>=。

 
      知识点4:P82,3.2.3,范围for(range for)语句(英文版91页)

      范围for语句用于遍历元素。形如:for(一个用于访问序列中基础元素的变量a : 被访问的序列对象b){statement..... blabla;}

首次初始化,变量a的值会被初始化为对象b序列中的第一个元素,迭代之后每次访问下一个元素,直到序列被完全访问结束。

可以使用auto &a的方式声明变量a,使变量绑定到具体的序列元素上,从而进行更改。如在for(auto a : str){}中,每次把a初始化的行为实质上是使a获得str每个元素的副本(拷贝),而for(auto &a : str){}这样的语句则使a成为了str对应的每个元素的"别名",从而可以修改str。

      使用范围for循环遍历多维数组,为了不手动打类名,也为了防止外层数组的名被auto类型转化成指针,要在对外层数组的访问上都加上&绑定。
 
      知识点5:P82,3.2.3,处理每个字符的头文件cctype(英文版91页)

      我们可以通过引用头文件<cctype>的形式处理每一个字符。这个头文件包含很多方便处理字符的函数。列举如下:

isalnum(c);    //当c是字母或者数字时为真      isalpha(c);     //当c是字母时为真

iscntrl(c);      //当c是控制字符时为真

isdigit(c);      //当c是数字时为真                    isgraph(c);    //当c不是空格但是可打印时为真

islower(c);    //当c是小写字母为真

isprint(c);      //当c可打印时为真                    isupper(c);    //当c是大写字符时为真

isxdigit(c);     //当c是16位数字时为真

ispunct(c);    //当c是标点符号时为真(一个字符除了控制字符,字母,数字,可打印空白就是标点符号)

isspace(c);    //当c是空白时为真(空白包括空格,横向/纵向制表符,回车符,换行符,进纸符)

tolower(c);    //把大写字符转换为小写字符,本来就是小写字符的不变,返回转换后的字符

toupper(c);   //把小写字符转换为大写字符,本来就是大写字符的不变,返回转换后的字符

 
      知识点6:P87,3.3,类模板、容器和实例化(英文版97页)

当我们在C++里面谈论容器这个概念时,我们应该知道容器是用来存储和组织一类特定对象的集合。下面提到的标准库类型vector,就是一个容器。

类模板一般用于按照模板规定好的规则生成不同的类。我们无需很麻烦的一个一个写类的定义,只需使用模板,给出指定的少量信息,类模板就会帮助我们自动生成一个我们可以直接使用的类。vector也是一个类模板。

通过类模板创建类的过程,或者通过类型创建对象的过程,就叫做实例化。

 
      知识点7:P87,3.3.1,定义和初始化vector对象(英文版97页)

      与string的定义和初始化一样,我们也可以使用多种方式定义和初始化一个vector对象。

     以下几种初始化语句被string支持:

vector<Type> v1;//创建了一个空的vector容器,这个容器是Type类型对象的集合,这个集合名字叫做为v1,执行默认初始化。

vector<Type> v2(v1);//创建了一个叫做v2的vector容器,这个容器的内容和v1相同。

vector<Type> v2=v1;//同上一句,拷贝初始化。

vector<Type> v3{a,b,c};//v3包含了初始值个数的元素。

vector <Type> v4={a,b,c};//同上。

vector <Type> v5(n,val);//直接初始化这个容器,操作后v5拥有n个元素,每个元素的值都是val。

vector <Type> v5(n);//直接初始化这个容器,操作后v5拥有n个元素,每个元素的值都被默认初始化。

当我们使用圆括号()初始化对象时,IDE会认为我们在通过语句“构建”这个对象  ;当我们使用花括号{ }初始化对象时,IDE会认为我们在初始化对象。

      当我们使用等号=初始化对象时,我们就执行了“拷贝初始化”;当我们不使用=初始化对象时,我们就执行了“直接初始化”。

      但是当我们在花括号里面给一个不符合对象类型的值,IDE就会认为我们正在构建而非初始化对象,一个体现就是,vector<string> s1{10};  这个语句中,10不能转换为string,因此被系统理解为“这个string容器里有10个元素”。当然,像vector <string>s1={10};这样的语句是错误的,因为=就应该是拷贝初始化了,然而10并不能够被转化为string因此也无法赋值。

 
      知识点8:P90,3.3.2,vector支持的操作(英文版102页)

      1.向容器的后面添加元素:已存在vector<T> v;,可以使用 v.push_back(vector<T> a)的方式在集合v的尾部添加元素。

2.empty和size函数成员:已存在vector<T> v;,可以使用 v.empty()的方式判断v是否为空,可以使用v.size()的方式返回v的大小。

3.重载的运算符:vector支持的运算符包括下标运算符[ ]、重载的+、==、!=、<、>、<=、>=。这一点和string类似。

 
     知识点9:P95,3.4.1,迭代器(英文版106页)
      为了访问容器的元素(有些容器可能不支持下标运算符),因此C++提供了迭代器这个概念来访问容器中的指定元素。
     支持迭代器的类都会提供名为begin和end的函数成员来供我们获取迭代器。如已定义vector<int> i1(10);这时使用auto ben=i1.begin();这个语句获取指向第一个字符的迭代器,使用auto end=i1.end();获取指向i1容器最后一个元素的下一个元素的迭代器,术语“尾后迭代器”。两个迭代器可以相减,但是两个迭代器相加后的行为是未定义的。

当使用vector <int> 创建类时,这个类的命名空间就是vector <int>,命名空间中的迭代器类型写作vector<int>::iterator。因为这个叫做"vector<int>::iterator"的迭代器类型名太长了也不好记,这里我们使用auto推导这个类型。用成员函数cbegin和cend可以推导出底层const迭代器,就是这个迭代器对迭代器指向的内容只读不写。第6章会详细说明。

     迭代器 对 迭代器指向的容器内容    可以像    指针 对 指针指向的数组元素一样使用。
     虽然数组不是直接支持迭代器的类型,但是可以引入<iterator>头文件,使用begin(数组名)和end(数组名)的方式获得指向数组第一个元素和尾后第一个元素的指针。因为大多数容器不支持下标运算符,所以使用迭代器访问容器等结构中的元素是最好的方法。
 
      知识点10:P102,3.5.1,一维数组的定义和初始化(英文版114页)

一维数组声明形式:类型名  数组名[一个常量]。比如int a[15];这里这个数组的名字是a,有15个元素,每个元素都是int型的。再比如int *a[15];这里a数组的15个元素都是int *型的,即指向int的指针,这样的指针有15个,构成了一个数组。虽然有指针数组,但是存在元素都是引用类型的数组

一维数组的初始化方式就是花括号初始化,形如int a[n]={1,2,3};大括号里面的内容就是初始化列表,n为数组大小,可以缺省,缺省时数组长度由初始化列表的元素个数决定。当初始化列表的值的个数比数组长度小,数组剩下的元素被初始化为默认的值,比如对于有10个元素的int型数组,如果只给出第一个元素的值,后几个元素将被初始化为0。

      当我们声明int a[]的时候代表通过数组名a访问这个数组。我们也可以定义指向数组的指针和指向数组的引用来间接访问这个数组。已有int arr[10];的情况下,int (*ptr) [10]=&arr;这条语句可以使指针ptr指向arr这整个数组。int (&ref)[10]=arr;则会使ref作为整个arr数组的引用。int *(&ref)[10]=arr;这个语句则是说ref是arr的引用,这个被引用的数组的类型是指针数组。
      auto a=一个数组名,a的类型将会是这个指针,指针指向的类型就是数组元素的类型。用decltype(一个数组名) a;这样的形式,a将会是和数组名属性一致的数组。在大部分运算中,数组名都会被转化成相应的指针类型。如*(ai+4)中,数组名ai是指向整个数组首元素的指针,这个指针+4就是向右侧移动4位,指针原来指向第一个元素,移动4位就指向了数组中的第五个元素。然后指向的值就是ai数组第五个元素的值,相当于ai[4]。
 
       知识点11:P111,3.5.5,用数组初始化vector对象    和     用string对象赋值字符数组(英文版124页)
       作为与旧代码的接口,C++提供了方便的把数组转化为vector对象的方法。在声明vector对象时,我们可以通过迭代器用一个数组初始化vector。在已经存在int oldarray[10];的情况下,声明的语句形如:vector<int> arr( begin(oldarray)  ,  end(oldarray)  );可以把arr初始化为oldarray。begin和end这两个函数在<iterator>头文件里,作用是返回数组的首元素/尾后指针。这种初始化接受两个参数:拷贝开始部分指针和结束部分的指针。我们也可以写形如int arr[10]={0};   vector <int> newarr( arr+1   ,  arr+6 );这种方式拷贝数组arr的第2~第5号元素,并用它们初始化newarr。
       类似地,我们可以通过string a("23333333\n");  const char *b=a.c_str();这样的语句使string型的a被赋值给字符数组指针b。返回结果是const是为了确保我们不会通过这个指针改动返回的字符数组的值。
 
 
     第三章补充说明的内容:
     1.关于命名空间作用域
     书上的之后的代码都默认它们已经使用了正确的命名空间作用域。关于书中使用到的命名空间作用域,书后的附录A1部分有列表标注了这些书中用到的命名空间作用域。
     2:默认初始化、值初始化、拷贝初始化、直接初始化和列表初始化
      本章介绍了这几种初始化概念,这里主要分析它们的区别。默认初始化依赖于类的默认行为,函数外定义的类如果没有初始值,将被默认初始化为这个类“0”这个概念对应的值。函数内定义的未初始化的值执行默认初始化时有两种情况,当这个类是C++内置类的时候,比如说int,float,这个值不被初始化;当这个类是用户定义的或者是我们使用类模板定义的,将被构造函数初始化。其中如果这个类的对象被隐式初始化为这个类意义上的零,对应的这种初始化的行为就叫做值初始化。
     拷贝初始化和直接初始化的区别只在于初始化语句带()、{}还是由=构成。使用小括号或者大括号初始化对象的行为叫做直接初始化。使用=号初始化对象的行为叫做拷贝初始化,其中,使用{}初始化对象的行为叫做列表初始化。特点是列表初始化的列表内容必须要和初始化对象的类型严格匹配。