C++ Primer Plus(嵌入式公开课)---第4章 复合类型

1017

C++ Primer Plus

  • 第1-3章
  • 第4章 复合类型
    • 4.1 数组
      • 4.1.1 概念、三要素、书写格式、下标
      • 4.1.2 数组的初始化规则:
      • 4.1.3 C++11中数组初始化方式
    • 4.2 字符串(C语言)
      • 4.2.1 在数组中使用字符串
        • 案例
      • 4.2.2 区分 ①字符数组 和 字符串;②字符常量 和 字符串常量;③sizeof() 和 strlen() 。
        • ①字符串 和 字符数组 的区别
        • ②字符常量 和 字符串常量的区别(字符串常量本质上是个地址)
        • ③sizeof() 和 strlen() 的区别
        • 综合案例
      • 4.2.3 字符串输入-->每次读取一行字符串输入(案例见`编程练习第1题`)
        • ①用cin输入字符串的问题:只能读一个单词
        • ②面向行的输入:cin.getline() --- 推荐☆☆
          • 案例:
          • 问题+结论:
        • ③面向行的输入:cin.get() ---推荐☆
          • 案例1:
          • 问题1+改进:
          • 案例2:
          • 问题2+改进:
          • 总结:
        • ④cin、cin.getline()、cin.get()总结☆☆☆(案例见`编程练习第1题`)
        • ⑤空行和其他问题
      • 4.2.5 混合输入字符串和数字(案例见`编程练习第1题`)
    • 4.3 string类(C++)
      • 4.3.1 C语言的字符串 和 C++的string类 的相同点
      • 4.3.2 string类的 赋值、拼接、附加
        • strlen() 和 str.size()、str.length()的区别
      • 4.3.3 string类I/O(案例见`编程练习第1题`)
    • 4.4 结构体struct
      • 4.4.1 结构体初始化
        • 方式1:(定义结构体 和 创建结构体变量 分开进行)
        • 方式2:(定义结构体 和 创建结构体变量 同时进行)
        • 方式3:(不推荐)
      • 4.4.2 结构体赋值
      • 4.4.5 结构体数组
      • 结构体数组的创建和初始化
      • 4.4.6 结构中的位字段
    • 4.5 共用体/联合体union
      • union基本概念
      • 补充:字节对齐
      • union 和 struct 的区别:
    • 4.6 枚举enum
      • 4.6.1 设置枚举量的值
      • 4.6.2 枚举的取值范围
      • 4.6.3 enum 和 int 的关系
    • 4.7 指针
      • 4.7.1 指针的 声明+初始化
      • 4.7.2 指针的危险
      • 4.7.3 指针和数字
      • 4.7.4 用new来分配内存
      • 4.7.5 用delete释放内存
      • 4.7.6 用new创建动态数组
        • 1.用new创建数组
        • 2.用指针访问数组元素
    • 4.8 指针、数组和指针算术
      • 4.8.1 指针和数组
        • 数组的地址:
        • 指针数组 & 数组指针 ---优先级:`()>[]>*`
      • 4.8.2 指针小结
      • 4.8.3 指针和字符串
      • 4.8.4 使用new创建动态结构体
        • 补充:访问结构体成员时,何时使用句点运算符,何时又使用箭头运算符?
        • 案例1(访问结构体成员):
        • 案例2(运行时创建结构体 优于 编译时创建结构体):
      • 4.8.5 自动存储、静态存储和动态存储
        • 1.自动存储(栈区)
        • 2.静态存储(全局区)
        • 3.动态存储(堆区/自由存储区)
    • 4.9 类型组合---数组、指针、结构体的组合
      • 案例1:
        • 代码:
        • 结果:
      • 案例2:
        • 代码:
        • 结果:
    • 4.10 数组的替代品
      • 4.10.1 模板类vector---存放在堆区
      • 4.10.2 模板类array(C++11)---存放在栈区
      • 4.10.3 比较数组、vector对象和array对象
        • 案例:
        • 案例总结:
        • 结果:
    • 4.11 总结
    • 4.12 复习题(第2、14、16、17题)
    • 4.13 编程练习
      • 第1题 字符数组/字符串 和 string类 如何通过cin读取一整行 ☆☆☆
      • 第9题 对字符数组进行内容赋值时要注意的事项
      • 第10题 array类的使用
  • 第5章 循环和关系表达式

第1-3章

点这里

第4章 复合类型

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第1张图片

4.1 数组

4.1.1 概念、三要素、书写格式、下标

①数组里的元素必须是同类型的,且在计算机中占用连续的内存空间
②数组三要素:元素的类型、数组名、数组大小
③声明数组的通用格式:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第2张图片
数组通过下标或者索引来访问元素,下标从0开始。

4.1.2 数组的初始化规则:

只有在定义数组时才能使用初始化,此后就不能再初始化了,也不能将一个数组赋给另一个数组
请添加图片描述

4.1.3 C++11中数组初始化方式

用大括号,并且等号可以省略。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第3张图片
此外,C++标准模板库(STL)提供了一种数组替代品—模板类vector,而C++11新增了模板类array

4.2 字符串(C语言)

4.2.1 在数组中使用字符串

要把字符串存储到数组中,最常用的方法有两种:
1.将数组初始化为字符串常量;
2.从文件或键盘输入把字符串读入到数组中。

案例

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第4张图片

4.2.2 区分 ①字符数组 和 字符串;②字符常量 和 字符串常量;③sizeof() 和 strlen() 。

①字符串 和 字符数组 的区别

C++笔记5:字符串 字符数组 string
字符数组不一定是字符串,但字符串一定是字符数组。

字符串以 空字符(null character) 结尾,空字符写作’\0’
请添加图片描述

②字符常量 和 字符串常量的区别(字符串常量本质上是个地址)

举例:
字符常量’S’是数字83的另一种写法;
字符串常量"S"由 字符’S’和’\0’ 组成,而且实际上"S"表示的是字符串的内存地址
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第5张图片

③sizeof() 和 strlen() 的区别

注意区分sizeof()和strlen():
sizeof()统计整个字符数组所占内存的大小,单位是字节;
使用strlen()的前提是必须是字符串,只统计字符数组中可见的字符(‘\0’之后的字符就是不可见的字符),而且不会把 空字符\0 计算在内。

综合案例

主要用代码测试:
①字符数组 和 字符串 的区别;
②字符常量 和 字符串常量 的区别;
③sizeof() 和 strlen() 的区别。

//1.字符数组&字符串
	char arr1[10] = { 'd','o','g','s' };//字符数组,不是字符串
	char arr2[10] = { 'd','o','g','\0','s'};//字符数组,字符串(有结束符\0)
	cout << sizeof(arr1) << endl;//10个字节,而不是4个字节,因为sizeof()统计的是整个数组的长度
	cout << sizeof(arr2) << endl;//10个字节
	cout << strlen(arr1) << endl;//4个字符 不会报错,会警告	C6054可能没有为字符串“arr1”添加字符串零终止符。
	cout << strlen(arr2) << endl;//3个字符 只统计到结束符前的位置上,而且不把结束符算在内
							//因为结束符后面还有内容,所以也会警告	C6054可能没有为字符串“arr2”添加字符串零终止符。

//2.字符常量&字符串常量
	//用双引号括起来的字符串常量会隐式的包括结尾的空字符\0
	cout << sizeof('s') << endl;//1个字节
	cout << sizeof("s") << endl;//2个字节 除了字符s,还有结束符\0
	//cout << strlen('s') << endl;//报错,strlen()是用于字符串的
	cout << strlen("s") << endl;//1个字符

4.2.3 字符串输入–>每次读取一行字符串输入(案例见编程练习第1题

①用cin输入字符串的问题:只能读一个单词

问题:
cin使用空白(空格、制表符、换行符)作为字符串输入结束的标志,这意味着用cin获取字符数组的输入内容时一次只能读取到一个单词(因为有的单词之间是空格,比如New York,Marco Reus,而cin把空格做为字符串输入结束的标志之一)。

解决方法:
可以用istream类中成员函数getline()和get(),这两个函数都可以读取一整行直到遇到换行符。其中,getline()会丢弃最后的换行符,而get()将把换行符也作为输入的内容传输给电脑。

②面向行的输入:cin.getline() — 推荐☆☆

4.3.3 string类I/O中会讲到getline();
这节介绍的是cin.getline()
语法:

cin.getline(name1, 20);//默认是以'\n'作为输入结束符
cin.getline(name5, 20,'_');//以'_'作为输入结束符

括号里的参数分别是用来存储输入行的数组的名称,第二个参数是要读取的字符数(第二个参数是用来避免超越数组的边界),第三个参数是输入结束符,默认是换行符’\n’。
注意:cin.getline()不会保留结束符

案例:

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第6张图片

问题+结论:

问题:
从上面的例子能看出如果一次输入的内容有多余的,就会被当作下一次输入的内容,归根结底是因为用下划线作为结束符。
结论:
所以一般不推荐使用下划线作为结束符

③面向行的输入:cin.get() —推荐☆

语法:

cin.get(name1, 20); //默认是以'\n'作为输入结束符
cin.get(name5, 20, '_' );  //以'_'作为输入结束符

括号里的参数分别是用来存储输入行的数组的名称,第二个参数是要读取的字符数(第二个参数是用来避免超越数组的边界),第三个参数是输入结束符,默认是换行符’\n’。
注意:cin.get()会保留结束符,将其放到输入队列中

案例1:

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第7张图片

问题1+改进:

问题1:
从上面的例子可以看出,结束符和多余的内容会被当做是下一次的输入内容,这样会造成下一次的输入出错。
结论:
针对这个问题的解决办法是cin.get()把结束符吃掉

案例2:

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第8张图片

问题2+改进:

问题2:
由于一般输入结束后都会回车,如果用下划线做结束符,并且下划线后面还有多余的内容,这样就算用cin.get()把结束符吃掉,后面多余的内容依然会影响到下一次输入。
结论:
所以不推荐用下划线或者别的符号做结束符

总结:

①首先,不论输入结束符是什么,都用.get()(可以读取一个字符)把这个结束符给读取掉,即跨过这个换行符;
②另外,不要把下划线或者其他符合作为结束符,直接用默认的回车符作为结束符最简单,没有那么多问题。
这样就为下一次输入做好充分的准备了:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第9张图片

④cin、cin.getline()、cin.get()总结☆☆☆(案例见编程练习第1题

读取字符串char name[20];的三种方法,推荐用第一种:(也可以看看第5章 - 5.9 编程练习 的 第7、7’、8、9题的汇总☆☆☆

char name[20]; cin.getline(name,20)☆☆ cin.get(name,20)☆ cin >> name;
多余的内容 当以下划线作为结束符时,输入时下划线后面多余的内容会影响下次输入。 当以下划线作为结束符时,输入时下划线后面多余的内容会影响下次输入。 不受多余的内容的影响
结束符 不受结束符的影响 因为会保留结束符,所以结束符和多余的内容会影响下次输入。 虽然cin只能读取一个单词,但也是以\n作为结束符,并且 \n会被存入到输入队列中 ,所以会影响到下次输入。
解决办法 对于多余的内容:
不要用下划线或者其他符号作为结束符
对于结束符的影响:
用.get()来消除
对于多余的内容:
不要用下划线或者其他符号作为结束符
对于结束符的影响:
用.get()来消除
示例: cin.getline(name1,20); cin.get(name2,20).get(); cin >> name3; cin.get();
                           
结论:
cincin.get()都需要.get()来消除结束符,而cin.getline()不需要,所以推荐用cin.getline(name,20);//把读取的整行内容给到数组name,一次最多读20个字节,这个20一般是字符数组name[20]的大小,是用来避免超越数组的边界

⑤空行和其他问题

有一个问题:如果输入的内容全是空格会怎么办,就没办法退出输入操作,其实如果想要空行,可以直接’\n’。

4.2.5 混合输入字符串和数字(案例见编程练习第1题

案例(这个案例不好,看编程练习第1题):
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第10张图片
问题又是结束符被当做下一次输入的内容,所以先用cin.get()吃掉第一次输入遗留的结束符(‘\n’):
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第11张图片
案例用的是cin.get(),应该用cin.getline(),不会保留结束符。

4.3 string类(C++)

4.3.1 C语言的字符串 和 C++的string类 的相同点

都可以通过下标的方式访问元素:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第12张图片

4.3.2 string类的 赋值、拼接、附加

不同于4.1.2 数组的初始化规则中不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象;

C语言库中的函数:(包含头文件#include)
strcpy(str1, str2);//复制
strcat(str1, str2);//附加

C++中的str3 = str1 + str2; 需要
C语言中的strcpy(str3, str1); strcat(str3, str2); 来实现。

string的赋值、拼接、附加:
请添加图片描述

strlen() 和 str.size()、str.length()的区别

C++笔记5:字符串 字符数组 string
strlen()函数,这是C语言里的库函数。
.size()函数和.length()函数:都是用来统计字符个数,也不包含结束符’\0’。

4.3.3 string类I/O(案例见编程练习第1题

两种方法:
string name3;
getline(cin,name3);//可以读取整行 或者
cin >> name3;//只能读取一个单词 都可以,其中cin是istream类的一个对象。

C语言的字符串输入 C++的字符串输入
章节 4.2.3 字符串输入–>每次读取一行字符串输入 4.3.3 string类I/O
读取整行 由于cin一次只能读取一个单词,想要读取一行要用cin.getline()或者cin.get() 由于cin>>str也是一次只能读取一个单词,想要读取一行要用getline()
(注意:没有cin.)
示例 ①char name1[20]; char name2[20];
②cin>>name1;//读一个单词
cin.getline(name1,20); 和
cin.get(name2,20).get();//读整行
①string name3;
②getline(cin,name3);//读整行
cin >> name3;//读一个单词
示例备注 name1和name2是C语言的char name[],20是要读取的字符数 cin是istream类的对象,name3是string类的对象
            

补充:
C++笔记12:C++中.txt和.csv文件的写入和读取
这里的getline() 中不是istream类的对象cin,而是ifstream类的对象ifs和stringstream类的对象ss:

ifstream ifs;//创建输入流对象
string line;//记录每行的内容
getline(ifs, line)//整行读取,默认以'\n'作为分隔符
stringstream ss;
string subStr;
getline(ss, subStr, ',')) {//3.以','作为分隔符,将当前行的内容分开

4.4 结构体struct

关键字:struct

创建结构包括两步:
①声明结构体—它描述并标记了能够存储在结构中的各种数据类型;
②创建结构变量。

①声明结构体:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第13张图片
②创建结构体变量(C++中可以省略关键字struct):
请添加图片描述
补充:
①结构体可以通过使用成员运算符(.) 来访问各个成员变量;
②访问类成员函数(如cin.getline())的方式是从访问结构成员变量(vincent.price)的方式衍生而来的。

4.4.1 结构体初始化

方式1:(定义结构体 和 创建结构体变量 分开进行)

#include
#include
using namespace std;

//结构体声明:
struct Students{
	string name;
	int age;
};
int main(){

//C语言的常规初始化方式:
	struct Students stu1 = {"xiaoming", 23};
	struct Students stu2 = {};//空结构体,内部成员全为空
	
//C++的常规初始化方式:
	Students stu3 = {"xiaohua", 25};
	Students stu4 = {};//空结构体
	
//C++11的初始化方式(可以不带等号=):
	Students stu5 = {"Reus", 26};//带=
	Students stu6 = {};//带=  空结构体
	Students stu7 {"Tom", 24};//省略=
	Students stu8 {};//省略= 空结构体

	system("pause");
	return 0;
}

方式2:(定义结构体 和 创建结构体变量 同时进行)

#include
#include
using namespace std;

//结构体声明:
struct Students{
	string name;
	int age;
}stu1,stu2;

int main(){

	stu1 = {"Reus", 23};
	stu2 = {"Messi", 25};
	
	system("pause");
	return 0;
}

方式3:(不推荐)

//结构体声明:
struct  
{
	string name;
	int age;
}stu1;

这样创建了一个名为stu1的结构体变量,可以使用成员运算符来访问它的成员(如stu1.name),但这种类型没有名称,因此以后无法创建这种类型的变量,所以不推荐。

4.4.2 结构体赋值

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第14张图片

4.4.5 结构体数组

结构体数组的创建和初始化

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第15张图片

4.4.6 结构中的位字段

为字段通常用在低级编程中。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第16张图片

4.5 共用体/联合体union

union基本概念

特点:联合体可以存储多种不同类型的数据,但同一时间只能存储其中的一种类型,也就是说结构体可以同时存储int和char,而共用体只能同时存储int或char
占用内存大小:共用体的长度为其最大成员的长度。
用途:当可能使用到两种或两种以上的数据类型、但又不会同时使用时,就可以用联合体,这样比较节省空间。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第17张图片

补充:字节对齐

下面的内容摘自2.结构体的sizeof
结构体的sizeof涉及到字节对齐问题。
为什么需要字节对齐?计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。

字节对齐的细节和编译器的实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
2) 结构体的每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要,编译器会在成员之间加上填充字节(internal adding)。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员后加上填充字节(trailing padding)。

注意:空结构体(不含数据成员)的sizeof值为1
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第18张图片

union 和 struct 的区别:

比较内容 struct union
初始化 可以一次性给所有成员赋值 一次只能给一个成员赋值
占用内存大小 见上面的字节对齐 联合体的长度为最大成员的长度

案例:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第19张图片

4.6 枚举enum

C++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。
解释:用const一次只能创建一个符号常量,而用enum可以一次性创建多个符号常量,这是最大的用处

4.6.1 设置枚举量的值

有五点要注意的:
①默认第一个枚举类型为0,后面没被初始化的枚举量比前面的大1;
②枚举量列举完的最后没有分号;
③枚举量也不能有pi=3.14,这里面只能是整数,不能是浮点型
④可以省略枚举类型的名称,原因见下图;
⑤可以创建多个值相同的枚举量。

第④点的原因如下:枚举更常被用来定义相关的符号常量,所以不需要创建枚举类型的变量,也就可以省略枚举类型的名称
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第20张图片
案例:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第21张图片

4.6.2 枚举的取值范围

1.先找到已经定义的枚举值中最大值(假设是)101,然后看比它大的所有2的次幂(64<101<128<256)中最小的一个数128,这个数减1,就是取值范围的上限127;
2.如果已经定义的枚举值中都是正数,那么取值范围的下限就是0;
3.如果已经定义的枚举值中有负数,就找到最小的那个负数(假设是)-19,然后看比它小的所有2的次幂(-16>-19>-32->-64)中最大的一个数-32,这个数加1,就是取值范围的下限-31。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第22张图片

4.6.3 enum 和 int 的关系

结论1:对于枚举,①只定义了赋值运算符,所以②枚举不能进行算术运算!!!
结论2:enum可以提升到int,然后赋值给int变量;但int不能赋给enum。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第23张图片

4.7 指针

计算机程序在存储数据时必须跟踪的3种基本属性:

  1. 信息存储在何处;
  2. 存储的值为多少;
  3. 存储的信息是什么类型。

OOP强调的是在运行阶段(而不是编译阶段)进行决策,好比选择参观哪些景点取决于天气和当思的心情;
而面向过程的编程强调在编译阶段进行决策,就好比不论天气如何,都要坚持预先设定的日程安排。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第24张图片
指针也是一种变量,只不过它存储的是其他变量的地址值,即指针变量名说到底就是一个地址。
指针包含两部分:指向(&p)和指向的值(*p)。

地址运算符&(取址运算符):应用于变量,可以获取改变量的地址;
间接值/解引用运算符*(取值运算符):应用于指针,可以得到改地址处存储的值。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第25张图片

4.7.1 指针的 声明+初始化

①C程序风格和C++风格:
注:对每个指针变量名,都需要使用一个*
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第26张图片
②指针变量
指针变量可以指向不同的数据类型(int 4字节、char 1字节),但指针变量自身的长度是固定的,即char和int的地址长度是相同的

③指针的初始化
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第27张图片

4.7.2 指针的危险

一定要在对指针应用解引用运算符(*)之前,将指针初始化为一个确定的、适当的地址,即要先让指针有个具体的指向,才能解引用指针变量p(输出 *p)
请添加图片描述
可以这样:

long num = 123;
long* fellow;//声明一个指针
fellow = &num ;//初始化指针fellow
*fellow = 234;
(但是最好是在声明的时候就进行初始化,否则很容易出现上图中的情形,那并不是个好主意。)

4.7.3 指针和数字

指针不是整型,虽然计算机通常把地址当做整数来处理,但从概念上看指针与整数是截然不同的类型
int* pt; pt = 0xB8000000;//错误,因为=右边是int,而左边是地址
int* pt; pt = (int *)0xB8000000;//正确,将int强制转换为地址类型
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第28张图片

4.7.4 用new来分配内存

分配内存:
C语言—malloc() ;C++ —new运算符
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第29张图片
为一个数据对象活得并制定分配内存的通用格式:
typeName * pointer_name = new typeName;//new返回的是指针,要用一个指针来接收
例如:int* p1 = new int; //new出来的内存地址给到指针变量p1

案例:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第30张图片
补充:
new分配的内存块–>堆(heap)
常规变量声明分配的内存块,如变量pd的值存储在–>栈(stack)
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第31张图片

4.7.5 用delete释放内存

语法:
int* ps = new ps;

delete ps;
注意:
①这里只是释放ps指向的内存,并没有删除指针ps本身,即ps可以重新指向一个新的内存
②不要用delete重复释放同一内存块,即不要尝试释放已经释放过的内存块
③也不要用delete释放声明变量所获得的内存,即只能用delete来释放使用new分配的内存
④再次使用已经delete过的指针p5时,要重新初始化,因为delete之后的指针p5相当于一个声明过但未初始化过的指针
⑤不要创建两个指向同一个内存块的指针,这很容易导致删除同一个内存块两次的错误。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第32张图片
补充:
delete p5;//这里delete只是释放p5指向的内存,不会删除指针p5本身,p5还可以指向别的内存块:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第33张图片
new的是同一块内存地址?
(4.8.4的案例2又不一样了)

4.7.6 用new创建动态数组

在程序运行时创建的数组,叫动态数组(dynamic array),即new出来的数组就叫动态数组。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第34张图片

那么如何使用C++的new运算符来创建动态数组,以及如果使用指针访问数组元素?

1.用new创建数组

int* num = new int[10];//创建一个包含10个int元素的数组,把数组(首元素)的地址赋给num
...
delete[] num;//释放整个数组,而不仅仅是指针指向的元素

如果用new [ ]为数组分配内存,则应适用delete [ ]来释放。

2.用指针访问数组元素

通过下标的方式访问数组元素:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第35张图片

4.8 指针、数组和指针算术

4.8.1 指针和数组

在C++中,数组名被解释为数组首元素的地址,
例如,数组int num[10]的地址为 num 或者 &num[0]
int* p = num;//指针p指向数组num[10],即此时p等于num 或者 &num[0]
指针p每加1,其增加的值等于指向的类型(int)占用的字节数(4个字节)。

num[0] 等于*p
num[1] 等于*(p+1)

注意:
对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度(在自己的电脑上,指针占8个字节),不论指针指向的是一个变量还是一个数组

案例:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第36张图片

数组的地址:

数组名被解释为数组首元素的地址,
对数组名应用地址运算符时,得到的是整个数组的地址。

例如:对于数组int num[10];
数组名num表示数组首元素num[0]的地址,即&num[0];
&num表示整个数组的地址。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第37张图片

指针数组 & 数组指针 —优先级:()>[]>*

书里是这么说的:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第38张图片

C++笔记3:C++核心编程 最下面的补充
(补充)指针数组 & 数组指针:
首先需要明确一个优先级顺序:()>[]>*,所以:
(*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一个一维数组,数组长度为n,这是“数组的指针”,即数组指针
*p[n]:根据优先级,先看[],则p是一个数组,再结合,这个数组的元素是指针类型,共n个元素,这是“指针的数组”,即指针数组

具体示例可以看这个:
C语言中int *p[n]和int (*p)[n]的区别。

4.8.2 指针小结

整个4.7节

4.8.3 指针和字符串

C++中,char数组名、char指针以及用引号括起来的字符串常量都被解释为字符串首字符的地址(类似于数组的地址是数组首元素的地址)。

①const char* bird = “dog”;//常量指针,指向的值固定
补充:指针常量和常量指针—C++笔记4:C++中const和指针
②一般来说,如果给cout提供一个指针,它将打印地址
但如果指针的类型为char*,则cout将显示指向的字符串
如果要显示字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int*。
因此ps显示的是字符串“fox”,而(int *)ps显示的是该字符串的地址
③ps = new char[strlen(animal) + 1];//new一个大小为strlen(animal) + 1的内存空间,为后面的内容拷贝做准备
④将animal赋给ps,并不会复制字符串,而只是复制地址,并且会失去访问new出来的新空间的唯一途径,因此这里要用strcpy() (–>4.10.3案例总结③ 和 编程练习第九题)
如果用string类就可以通过直接赋值操作(str2 = str1)完成字符串的复制。

案例:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第39张图片

4.8.4 使用new创建动态结构体

通过使用new,可以创建动态结构体。
“动态”意味着内存是在运行阶段(而不是编译阶段)分配的。

将new用于结构体由两步组成:

1.用new创建结构体
Students *ps = new Students;
//new一个Students结构体,并将其地址赋给指针ps

2.访问结构体成员

struct Students{
	int num;
	char gender;
};
int main(){
	//声明+初始化一个结构体变量stu1:
	Students stu1 = {23,'M'};
	//结构体指针pt指向结构体变量stu1:
	Students* pt = &stu1;
}

访问结构体成员num的几种方式:
stu1.num //方式①
pt->num //方式②
(*pt).num //方式③

补充:访问结构体成员时,何时使用句点运算符,何时又使用箭头运算符?

问:访问结构体成员时,何时使用句点运算符,何时又使用箭头运算符?
答:如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第40张图片

案例1(访问结构体成员):

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第41张图片

案例2(运行时创建结构体 优于 编译时创建结构体):

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第42张图片
案例代码:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第43张图片
这里两次new的地址又不一样了
(4.7.5的补充:几次new的内存地址是一样的)
书上是这么写的:
C++不保证新释放的内存就是下次使用new时选择的内存。
另外,最好不要把new和delete放在不同的函数中,因为这样很容易忘记使用delete
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第44张图片

4.8.5 自动存储、静态存储和动态存储

C++有三种管理数据内存的方式:自动存储、静态存储和动态存储(也叫作自由存储空间或堆)。C++新增了第四种类型—线程存储。
C++笔记3:C++核心编程

1.自动存储(栈区)

在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable)。
自动变量是一个局部变量,存储在栈中,遵循后进先出(LIFO)的原则。

2.静态存储(全局区)

静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:
一种是在函数外面定义它(全局变量);
另一种是在声明变量时使用关键字static。

3.动态存储(堆区/自由存储区)

new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。他们管理了一个内存池,C++称之为自动存储空间(free store)或堆(heap)。
为了避免内存泄漏,要养成一种好习惯,即同时使用new和delete运算符,在自由存储空间上动态分配内存,随后便释放它。C++智能指针有助于自动完成这种任务,在16章有介绍。

4.9 类型组合—数组、指针、结构体的组合

案例1:

数组、结构、指针组合在一起:
①结构体变量、②结构体指针、③结构体数组。

代码:

#include
#include
using namespace std;

//定义结构体:
struct Students {
	int m;
	double n;
	char ch;
};
int main() {
//4.9 类型组合---案例1:
	Students stu1;//创建1个结构体变量
	stu1.m = 10;

	Students* p1 = &stu1;//结构体指针p1指向结构体变量stu1
	p1->n = 11;
	(*p1).ch = 'M';

	cout << "stu1.m = " << stu1.m << "\tstu1.n = " << p1->n << "\tstu1.ch = " << (*p1).ch << endl;
	
	Students stu[3] = {};//创建一个结构体数组
	stu[0].m = 1;
	stu[1].n = 2.3;
	stu[2].ch = 'A';
	for (int i = 0; i < 3; i++) {
		cout << "stu[" << i << "].m = " << stu[i].m << "\tstu[" << i << "].n = " << stu[i].n
			 << "\tstu[" << i << "].ch = " << stu[i].ch << endl;
	}
	cout << endl;
	
	system("pause");
	return 0;
}

结果:

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第45张图片

案例2:

结构体变量:Students S01,S02,S03;
①创建一个指向结构体变量的指针—结构体指针;例如Students *p1 = &S01
②创建一个结构体数组;例如Students stu[3] = {};
③创建一个指向结构体数组的指针(一个指针,指向一个结构体数组);例如Students *p2 = stu;
④创建一个结构体指针数组(*一个数组,里面存放多个结构体指针,且是指向常量的结构体指针,即常量指针const p) ;例如,const *p[3] = {&S01,&S02,&S03};

⑤创建一个指向结构体指针数组的指针(一个指针,指向一个存放多个结构体指针的数组);例如const **ppa = p;
⑥C++ 11中的自动类型推理auto;例如auto ppb = p;

代码:

#include
#include
using namespace std;

//定义结构体:
struct Students {
	int m;
};
int main() {
//4.9 类型组合---案例2:
//结构体变量+①结构体指针:
	Students s01, s02, s03;//创建三个结构体变量
	s01.m = 2010;
	Students* p1 = &s02;//①结构体指针
	p1->m = 2012;
	Students* p2 = &s03;//①结构体指针
	(*p2).m = 2014;
	cout << "结构体变量+结构体指针:\ns01.m = " << s01.m << "\ts02.m = " << p1->m << "\ts03.m = " << (*p2).m << endl;
	cout << endl;

//②结构体数组+③指向结构体数组的指针:
	Students stu[3] = {};//②结构体数组
	stu[0].m = 2016;
	stu[1].m = 2016;
	stu[2].m = 2016;
	Students* p3 = stu; //③指向结构体数组的指针
	p3->m = 2018;
	(p3 + 1)->m = 2018;
	(p3 + 2)->m = 2018;
	(*p3).m = 2015;
	(*(p3 + 1)).m = 2015;
	(*(p3 + 2)).m = 2015;
	cout << "结构体数组+结构体指针:" << endl;
	for (int i = 0; i < 3; i++) 
		cout << "stu[" << i << "].m = " << stu[i].m << endl;
	cout << endl;

//④结构体指针数组+⑤指向结构体指针数组的指针:
	Students s04, s05, s06;//创建三个结构体变量
	s04.m = 2001;	s05.m = 2003;	s06.m = 2005;
	const Students* p[3] = { &s04, &s05, &s06 };//④结构体指针数组
	//p[0]->m = 2002;//报错!! 表达式必须是可修改的左值  常量指针:指向的内容值固定的,所以不能修改p[0]指向的s04的内容
	cout << "s04.m = " << p[0]->m << "\ts05.m = " << (*p[1]).m << "\ts06.m = " << (*p[2]).m << endl;
	const Students** ppa = p;//⑤指向结构体指针数组的指针
	cout << "s04.m = " << (*ppa)->m << "\ts05.m = " << (*(ppa + 1))->m;
	auto ppb = p;//C++ 11中的自动类型推理
	cout << "\ts06.m = " << (*(ppb + 2))->m << endl;

	system("pause");
	return 0;
}

结果:

①创建一个指向结构体变量的指针;
//地址:
p1 = &s02
p2 = &s03
//结构体变量:
*p1 = s02
*p2 = s03
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第46张图片
②创建一个结构体数组;
③创建一个指向结构体数组的指针;
//地址:
p3 = &stu[0] //这里的=是等价于的意思
p3 + 1 = &stu[1]
p3 + 2 = &stu[2]
//结构体变量:
*p3 = stu[0]
*(p3 + 1) = stu[1]
*(p3 + 2) = stu[2]
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第47张图片
④创建一个结构体指针数组;
⑤创建一个指向结构体指针数组的指针;
⑥C++ 11中的自动类型推理auto
//一级地址:
p[0] = &s04
p[1] = &s05
p[2] = &s06
//结构体变量:
*p[0] = s04
*p[1] = s05
*p[2] = s06
//二级地址:
ppa = &p[0]
ppa + 1 = &p[1]
ppa + 2 = &p[2]
//一级地址:
*ppa = p[0]
*(ppa + 1) = p[1]
*(ppa + 2) = p[2]

//C++ 11中的自动类型推理:
auto ppb = p;//C++11 automatic type deduction 编译器能够正确地推断出ppb的类型
//二级地址:
ppb = &p[0]
ppb + 1 = &p[1]
ppb + 2 = &p[2]
//一级地址:
*ppb = p[0]
*(ppb + 1) = p[1]
*(ppb + 2) = p[2]
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第48张图片

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第49张图片

4.10 数组的替代品

4.10.1 模板类vector—存放在堆区

模板类vector类也是一种动态数组,它是用new创建的动态数组的替代品。
vector类的对象存储在堆区或自由存储区
在这里插入图片描述
使用vector类,要包含头文件#include
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第50张图片

4.10.2 模板类array(C++11)—存放在栈区

array类的对象和数组都存储在栈区中。
array类的效率与数组相同,但更方便、更安全。
使用array类要包含头文件#include
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第51张图片

4.10.3 比较数组、vector对象和array对象

1.模板类vector动态数组的替代品, 都存储在堆区或自由存储区:

int* arr1 = new int[20];//动态数组
vector<int> v1(20);//vector对象

2.模板类array定长数组的替代品,都存储在栈区:

int arr2[20];//定长数组
arra<int, 20> a1;//array对象

案例:

先看一个案例,代码如下:

#include
#include//STL C++ 98
#include//C++ 11
using namespace std;

int main() {
//4.10数组的替代品:
	//original C++
	double a1[4] = { 1.2,2.4,3.6,4.8 };
	//C++ 98 STL:
	vector<double> a2(4);
	a2[0] = 1.0 / 3.0;
	a2[1] = 1.0 / 5.0;
	a2[2] = 1.0 / 7.0;
	a2[3] = 1.0 / 9.0;
	//C++ 11
	array<double, 4>a3 = { 3.14,2.72,1.62,1.41 };
	array<double, 4>a4;
	a4 = a3;

	cout << "a1[2] = " << a1[2] << ", &a1[2] = " << &a1[2] << endl;
	cout << "a2[2] = " << a2[2] << ", &a2[2] = " << &a2[2] << endl;
	cout << "a3[2] = " << a3[2] << ", &a3[2] = " << &a3[2] << endl;
	cout << "a4[2] = " << a4[2] << ", &a4[2] = " << &a4[2] << endl;
	cout << endl;

	//a1[-2] = 20.2;//C++不检查这种越级错误,所以这样写不安全
	a2.at(2) = 1.0 / 3.0;
	a3.at(2) = 2.56;
	cout << "a2[2] = " << a2[2] << ", &a2[2] = " << &a2[2] << endl;
	cout << "a3[2] = " << a3[2] << ", &a3[2] = " << &a3[2] << endl;
	cout << "a4[2] = " << a4[2] << ", &a4[2] = " << &a4[2] << endl;

	system("pause");
	return 0;
}

案例总结:

①不论是数组、vector对象还是array对象,都可以使用标准数组表示来访问各个元素;
②从地址可知,array对象数组都存储在栈区中,而vector对象存储在堆区或自由存储区中;
③可以将一个array对象赋给另一个array对象,而对于数组,必须逐元素复制数据,否则只会复制地址(–>4.8.3 的④ 和 编程练习第九题);
④使用成员函数at()来赋值,具体见C++笔记8:C++提高编程2:STL—标准模板库中3.2 vector容器 存取操作:v1[i] 或者 v1.at()
⑤C++不检查这种越界错误,所以这样写a[-2] = 5;不安全。

结果:

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第52张图片

4.11 总结

4.1-4.10的内容。

容易混淆的内容:

  1. 指针数组&数组指针

  2. ③sizeof() 和 strlen() 的区别

  3. strlen() 和 str.size()、str.length()的区别

  4. 指针常量&常量指针

  5. 4.8.3 指针和字符串中的
    打印字符串的内容:cout << “hello,world.” ;
    打印字符串的地址:cout << (int *)“hello,world.” ;
    C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第53张图片

4.12 复习题(第2、14、16、17题)

2.使用模板类array声明一个由30个char组成的名为actor的数组:
array arr1;
使用模板类vector声明一个由10个int组成的名为actor的数组:
vector v1(10); //注意,这里是圆括号,不是方括号!

14.输出字符串的内容和字符串的地址
char name[20] = “dinglele”;
打印字符串的内容:cout << name ;
打印字符串的地址:cout << (int *)name ;
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第54张图片
16.cin.getline() 和 cin.get() 和 cin的区别
(案例见编程练习第1题)

17.不用using,程序该怎么写?并且要求用const指定数量。
const int num = 10;
std::vectorstd::string v1(num);//用模板类vector
std::arraystd::string,num arr1;//用模板类array

4.13 编程练习

第1题 字符数组/字符串 和 string类 如何通过cin读取一整行 ☆☆☆

(第五章-编程练习-第7题,跟这个类似)
(4.2.3 的cin、cin.getline()、cin.get()总结4.3.3 string类I/O—getline())
结论:
字符数组:char name[20];
cin>>name;//一次只能读一个单词,遇到空白字符(空格符、制表符、换行符)就停止输入,并且会保留结束符;
cin.get(name,20);//一次可以读一整行,但也会保留结束符;
所以cin>>name;cin.get(name,20)都需要cin.get()来消除结束符;
cin.getline(name,20);可以读取一整行并且不会保留结束符;
所以推荐用cin.getline(name,20);//把读取的整行内容给到数组name,一次最多读20个字节,这个20一般是字符数组name[20]的大小,是用来避免超越数组的边界。

string类也是类似:string str;
cin>>str;//一次只能读一个单词,遇到空白字符(空格符、制表符、换行符)就停止输入,并且会保留结束符,所以需要cin.get()来消除结束符;
getline(cin,name);//一次可以读一整行并且不会保留结束符;
所以推荐用getline(cin,name);

综上,
要用cin>>name;cin>>str;,后面就要加个cin.get()来消除结束符,但只能读一个单词;
要么就直接用cin.getline(name,20);getline(cin,name);来读取整行;
最好直接用第二种,最保险。

C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第55张图片
示例代码:

//字符数组,字符串:
	char name1[20], name2[25];
	cout << "请输入name1:";
	cin >> name1;//只能接收一个单词,遇到空格或回车符就会停止,并且会保留这个空格或回车符
	cin.get();//消除结束符
	cout << "name1 = " << name1 << endl;
	cout << "请输入name2:";
	cin.getline(name2,20);//可以接收整行,遇到回车符停止
	cout << "name2 = " << name2 << endl;
//string类:
	string name3,name4;
	cout << "请输入name3:";
	cin>>name3;//只能接收一个单词,遇到空格或回车符就会停止,并且会保留这个空格或回车符
	cin.get();//消除结束符
	cout << "name3 = " << name3 << endl;
	cout << "请输入name4:";
	getline(cin, name4);//可以接收整行,遇到回车符停止
	cout << "name4 = " << name4 << endl;
	

效果:
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第56张图片

第9题 对字符数组进行内容赋值时要注意的事项

对字符数组进行内容赋值时不能直接candies2->brand = "12le里";//这样只会把字符串的地址赋给brand,并且会失去访问new出来的新空间的唯一途径,这里需要用strcpy();来实现对字符数组内容的赋值。
而如果这里的brand是string类,就可以直接candies2->brand = "12le里";

示例代码:

struct CandyBar {
	char brand[80];
	float weight;
	int calorie;
};
int main() {

//第九题:
	CandyBar* candies2 = new CandyBar[3];
	//candies2->brand = "12le里";//因为结构体成员brand是一个字符数组,所以并不会复制字符串,而只是复制地址,
								 //并且会失去访问new出来的新空间的唯一途径,因此这里要用strcpy()
	strcpy(candies2->brand,"12le里");//
	(candies2 + 1)->weight = 125.6;
	(candies2 + 2)->calorie=25.6;
	cout << candies2->brand << (candies2 + 1)->weight << (candies2 + 2)->calorie << endl;
	delete[] candies2;//这个给忘了!!!
	
	system("pause");
	return 0;
}

new完别忘了delete!!!
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第57张图片

第10题 array类的使用

4.10.3 比较数组、vector对象和array对象:
③可以将一个array对象赋给另一个array对象,而对于数组,必须逐元素复制数据,否则只会复制地址;
C++ Primer Plus(嵌入式公开课)---第4章 复合类型_第58张图片
示例代码:

	//第十题:
	array<float, 3> arr_score;
	float ave = 0;
	for (int i = 0; i < arr_score.size(); i++) {
		cout << "请输入第" << i + 1 << "次成绩:";
		cin >> arr_score[i];
		ave += arr_score[i];
	}
	ave /= arr_score.size();
	cout << "您一共输入了 " << arr_score.size() << " 次成绩,平均分是 " << ave << " 分。" <<  endl;
	

第5章 循环和关系表达式

点这里

你可能感兴趣的:(C++,Primer,Plus,c++)