前言
个人觉得学习编程最有效的方法是阅读专业的书籍,通过阅读专业书籍可以构建更加系统化的知识体系。
一直以来都很想深入学习一下C++,将其作为自己的主力开发语言。现在为了完成自己这一直以来的心愿,准备认真学习《C++PrimerPlus》。
为了提高学习效率,在学习的过程中将通过发布学习笔记的方式,持续记录自己学习C++的过程。
本篇前言
本章首先将介绍除类以外的所有复合类型,以及将介绍new和delete及如何使用它们来管理数据。另外,还将简要地介绍string类,它提供了另一种处理字符串的途径。
一、数组
数组(array)是一种数据格式,能够存储多个同类型的值。
要创建数组,可使用声明语句。数组声明应指出以下三点:
存储在每个元素中的值的类型
数组名
数组中的元素数
声明数组的通用格式如下:
typeNamearrayName[arraySize]
表达式arraySize指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8*sizeof(int)),即其中所有的值在编译时都是已知的。具体示例如下代码:
shortmonths[12];
通过以上代码,声明了一个名为months的拥有12个short类型值的数组。
通过使用下标或索引可以单独访问数组元素。C++规定数组从0开始编号。C++使用带索引的方括号表示法来指定数组元素。例如:months[0]是months数组的第一个元素,months[11]是months数组的最后一个元素。因为开始数字为0,加上元素数量12个,最后一个元素比总数量少1。
在编写代码的过程中要注意下标值是否有效,例如:months[-1]、months[12]就是无效的下标值,这可能会导致程序出现异常。
接下来我们根据程序清单4.1了解数组的一些属性,包括声明数组、给数组元素赋值以及初始化数组:
//arrayone.cpp--小型整数数组#includeiostreamintmain(){usingnamespacestd;intorange[2];//创建有2个元素的数组orange[0]=15;//赋值给第1个元素orange[1]=28;intorangeCosts[2]={,2};//创建并初始化数组cout"橙子的总数量=";coutorange[0]+orange[1]endl;cout"第一个包装里有"orange[0]"个橙子,";cout"其中每个橙子的成本为"orangeCosts[0]"元。\n";cout"第二个包装里有"orange[1]"个橙子,";cout"其中每个橙子的成本为"orangeCosts[1]"元。\n";inttotal=orange[0]*orangeCosts[0]+orange[1]*orangeCosts[1];cout"全部橙子的总成本为"total"元。\n";cout"\norange数组的长度="sizeoforange;cout"bytes.\n";cout"第一个数组的长度="sizeoforange[0];cout"bytes.\n";return0;}
运行结果如下:
橙子的总数量=4第一个包装里有15个橙子,其中每个橙子的成本为元。第二个包装里有28个橙子,其中每个橙子的成本为2元。全部橙子的总成本为元。orange数组的长度=8bytes.第一个数组的长度=4bytes.
1、程序说明
该程序首先创建一个名为orange包含2个int类型元素的数组。然后我们根据下标值分别对2个元素进行了赋值。因为orange的每个元素都是int类型,因此能够将数值赋值给元素、并将它们相加和相乘。
程序在给数组中元素赋值时,采用了两种方式,分别是通过下标值对每个元素进行赋值和提供一个用逗号分隔的值列表(初始化列表),并将它们用花括号括起即可。
接下来,程序通过下标值对数组中元素进行访问并进行一些计算。
sizeof运算符返回类型或数据对象的长度(单位为字节)。将sizeof运算符用于数组名,得到的将是整个数组中的字节数,如果将sizeof用于数组元素,则得到的将是元素的长度(单位为字节)。
2、数组的初始化规则
C++只有在定义数组时才能使用初始化,此后只能通过下标值分别对单个元素进行赋值:
intorangeCosts[2]={,2};//有效intbananaCosts[2];//有效bananaCosts[2]={7,10};//无效bananaCosts=orangeCosts;//无效bananaCosts[0]=7;//有效bananaCosts[1]=10;//有效
初始化数组时,提供的值可以少于数组的元素数目。例如,下面的语句只初始化orangeCosts数组的第一个元素:
intorangeCosts[2]={};//有效
以上代码,将第一个元素赋值为,第二个元素编译器默认赋值为0。
如果初始化数组时方括号内([])为空,C++编译器将计算元素个数。例如:
intappleCosts={2,5};
以上代码,编译器将使appleCosts数组包含2个元素。
、C++11数组初始化方法
C++11将使用大括号的初始化(列表初始化)作为一种通用初始化方式,可用于所有类型。C++11在以前列表初始化的基础上增加了一些新的功能:
初始化数组时,可省略等号(=):
intappleCosts[2]{2,5};
可不在大括号内包含任何东西,这将把所有元素都设置为零
intappleCosts[2]{};
列表初始化禁止缩窄转换:
intappleCosts[2]{2.0,5};
在上述代码不能通过编译,因为将浮点数转换为整型是缩窄操作,即使浮点数的小数点后面为零。
二、字符串
字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种:1、继承自C语言的C风格字符串;2、基于string类库的方法。
C风格字符串具有一种特殊的性质:以空字符(nullcharacter)结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾,如下代码:
charhi[2]={h,i};//不是字符串charhello[6]={h,e,l,l,o,\0};//是字符串
以上方法初始化字符串的方法较为复杂,可以使用引号括起字符串即可。这种字符串被称为字符串常量(stringconstant)或字符串字面值(stringliteral),如下代码:
charhello[6]="hello";chargood[]="good";//让编译器计算长度
用引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它。
C++对字符串的长度没有限制,处理字符串的函数根据空字符的位置判断字符串是否结束。在确定存储字符串所需的最短数组时,需要将结尾的空字符计算在内。
注意:字符串常量(使用双引号)不能与字符常量(使用单引号)互换。
1、拼接字符串常量
有时候,字符串很长,无法放到一行中。C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。事实上,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。因此,,下面所有的输出语句都是等效的:
cout"hiiam""kangkang\n";cout"hiiam""kangkang\n";cout"hiiam""kangkang\n";
注意,拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\0)后面。第一个字符串中的\0字符将被第二个字符串的第一个字符取代。
2、在数组中使用字符串
要将字符串存储到数组中,最常用的方法有两种将数组初始化为字符串常量、将键盘或文件输入读人到数组中。代码如下:
//strings.cpp--在数组中存储字符串#includeiostream#includecstring//strlen()函数所在头文件intmain(){usingnamespacestd;constintSize=15;charname1[Size];//空数组charname2[Size]="Jane";//初始化数组cout"你好!我是"name2;cout"!请问你叫什么名字?\n";cinname1;cout"我叫"name1",我的名字占";coutstrlen(name1)"个字符";cout",在存储数组中占"sizeof(name1)"字节。\n";cout"第一个字符是"name1[0]"。\n";name2[1]=\0;//设置空字符串cout"很高兴认识你,我的第一个字符是";coutname2endl;return0;}
运行结果如下:
你好!我是Jane!请问你叫什么名字?Kang我叫Kang,我的名字占4个字符,在存储数组中占15字节。第一个字符是K。很高兴认识你,我的第一个字符是J
通过使用sizeof运算符可以计算出数组的长度,strlen()函数只计算存储在数组中可见的字符的长度。
、字符串输入
由于不能通过键盘输入空字符,cin使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着cin在读取字符数组输入时如果存在空白则会导致,只读取空白之前的内容放到数组中,并自动在结尾添加空字符。例如:
//instr1.cpp--读取多个字符串#includeiostreamintmain(){usingnamespacestd;constintArSize=20;charname[ArSize];charcity[ArSize];cout"请输入你的姓名(姓和名空格分开):\n";cinname;cout"输入你的城市:\n";cincity;cout"请确定输入内容:姓名为"name",所在城市为"cityendl;return0;}
运行结果如下:
请输入你的姓名(姓和名空格分开):张三输入你的城市:请确定输入内容:姓名为张,所在城市为三
出现以上情况,就是因为张和三字之间有空白。cin在读取字符数组输入时遇到空白时,将读取的空白之前的内容放到数组中,并自动在结尾添加空字符,而空白之后的内容在遇到下一个cin时又会重复上面的规则,这才导致三字被写入city数组中。
要解决以上问题有两种方法:getline()函数和get()函数,这两个函数都读取一行输入,直到到达换行符。然而,随后getline()将丢弃换行符,而get()将换行符保留在输入序列中。
将上述代码分别用getline()函数和get()函数替换如下:
//instr1.cpp--读取多个字符串#includeiostreamintmain(){usingnamespacestd;constintArSize=20;charname[ArSize];charcity[ArSize];cout"请输入你的姓名(姓和名空格分开):\n";cin.get(name,20).get();cout"输入你的城市:\n";cin.getline(city,20);cout"请确定输入内容:姓名为"name",所在城市为"cityendl;return0;}
运行结果如下:
请输入你的姓名(姓和名空格分开):张三输入你的城市:北京请确定输入内容:姓名为张三,所在城市为北京
因为get()将换行符保留在输入序列中,需要调用get()将尾部的换行符读取掉。否则还是会出现上面的情况,无法输入城市的内容。
三、string类简介
string类定义隐藏了字符串的数组性质,可以像处理普通变量那样处理字符串。
使用string对象更方便,也更安全。从理论上说,可以将char数组视为一组这使得与使用数组相比,用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体
1、C++11字符串初始化
C++11也允许将列表初始化用于C风格字符串和string对象,代码如下:
charhi[]={"hi"};stringhello={"hello"};
2、赋值、拼接和附加
使用string类时,可以将一个string对象赋给另一个string对象,代码如下:
stringstr1="test";stringstr2=str1;
string类简化了字符串的合并操作。可以使用运算符+或者+=将两个string对象合并起来,代码如下:
stringstr=str1+str2;str1+=str2;
、string类的其他操作
接下来我们对比一下string类和字符数组的不同,代码如下:
//strtype.cpp--更多字符串类特性#includeiostream#includestring//string类的头文件#includecstring//C风格字符串头文件intmain(){usingnamespacestd;charcharr1[20];charcharr2[20]="jaguar";stringstr1;stringstr2="panther";//string对象和字符数组的赋值str1=str2;//将str2复制给str1strcpy(charr1,charr2);//将charr2复制给charr1//用于string对象和字符数组的追加str1+="paste";//将“paste”追加到str1结尾strcat(charr1,"juice");//将“juice”追加到charr1结尾//查找string对象和C风格字符串的长度intlen1=str1.size();//获取str1长度intlen2=strlen(charr1);//获取charr1长度cout"字符串"str1"包含"len1"个字符。\n";cout"字符串"charr1"包含"len2"个字符。\n";return0;}
运行结果如下:
字符串pantherpaste包含1个字符。字符串jaguarjuice包含12个字符。
从上述代码,我们可以看出string对象的语法通常比使用C字符串函数简单,同时因为string对象会自动调整大小,相对于字符数组一不小心就超出目标数组大小,显得更为安全。
其中还有一点差别,就是在计算字符串的长度时,str1不是被用作函数的参数,而是通过句点.连接了size()方法。这是因为str1作为string类对象,可以使用对象名和句点.运算符来指出方法要使用哪个字符串,换一种说法就是string类对象通过句点.运算符可以调用string类中的方法。
4、string类I/O
未初始化的数组的内容是未定义的;其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。
将输入读取到string对象,还可以通过如下方式:
getline(cin,str1);
5、其他形式的字符串字面值
C++有很多类型的字符串字面值,具体如下:
charstr[]="hello";wchar_tstr1[]=L"hello";char16_tstr2[]=u"hello";char2_tstr[]=U"hello";
C++11新增的另一种类型是原始(raw)字符串。将"(和)"作为定界符,并使用前缀R来标识原始字符串:
coutR"("Apple"的意思是苹果。)"endl;
运行结果如下
"Apple"的意思是苹果。
根据运行结果我们可以看到,"(和)"定界符之间的内容被原封不动的进行了输出。但当我们需要在"(和)"定界符之间输入)"时,可以在原始字符串语法的"和(之间添加其他字符,同时结尾"和)之间也必须包含这些字符。例如在"和(之间添加+*,需要使用R"+*(和)+*",示例代码如下:
coutR"+*("(Apple)"的意思是苹果。)+*"endl;
运行结果如下
"(Apple)"的意思是苹果。
四、结构简介
当我们有一组不同类型的数据需要存储时,可以使用C++中的结构类型。
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。因此创建结构包括两步。首先,定义结构描述——它描评并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量(结构数据对象)。最后可以定义结构类型后,通过成员运算符(.)来访问各个成员。
结构定义语法:
struct结构名{//数据类型}
1、在程序中使用结构
//structur.cpp--一个简单的结构#includeiostreamstructPerson//声明结构{charname[20];intage;};intmain(){usingnamespacestd;PersonbjPerson={"张三",//name值18//age值};PersonshPerson={"李四",2};cout"请北京来的同学介绍一下自己:""\n";cout"大叫好,我是"bjPerson.name",今年"bjPerson.age"岁\n";cout"接下来请上海来的同学介绍一下自己:""\n";cout"大叫好,我是"shPerson.name",今年"shPerson.age"岁\n";return0;}
运行结果如下:
请北京来的同学介绍一下自己:大叫好,我是张三,今年18岁接下来请上海来的同学介绍一下自己:大叫好,我是李四,今年2岁
在使用结构之前,首先声明结构,然后如声明数组变量一样,使用结构类型名加上变量名即可表明一个结构类型,接着和数组一样,使用由逗号分隔值列表,并将这些值用花括号括起。
要调用结构类型中的成员,使用成员运算符(.)即可。
2、C++11结构初始化
与数组一样,C++11也支持将列表初始化用于结构,且等号(=)是可选的:
PersonbjPerson{"张三",18};
当大括号内未包含任何东西,各个成员都将被设置为零。
PersonbjPerson{};
bjPerson.name的每个字节都被设置为零,bjPerson.age被设置为零。
同数组一样,不允许缩窄转换。
、结构可以将string类作为成员吗
只要编译器支持对以string对象作为成员的结构进行初始化:
#includestringstructPerson//声明结构{std::stringname;intage;};
4、其他结构属性
C++使用户定义的类到与内置类型尽可能相似。例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以使用赋值运算行(=)将结构赋给另一个同类型的结构,这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组。这种赋值被称为成员赋值(assignment))。
5、结构数组
结构是用户定义的类型,也可以如其他基本类型一样,创建对应的数组,代码如下:
Personpersons[2]={{"张三",18},{"李四",2}};cout"请第一位同学介绍一下自己:""\n";cout"大叫好,我是"persons[0].name",今年"persons[0].age"岁\n";
6、结构中的位字段
字段的类型应为整型或枚举,接下来是冒号,冒号后面是指定了使用位数的数字。可以使用没有名称的字段提供间距。每个成员都被称为位字段(bitfield)。示例代码:
structComputerInfo{unsignedintSN:4;//序列号占4bitunsignedint:4;//空白间距占4bitunsignedintVersion:1;//版本号占1bit}
可以像通常那样初始化这些字段:
ComputerInfo