C++ Primer Plus 第四章 复合类型

数组

// 声明数组的格式:  typeName arrayName[arraySize];		
// arraySize不能是变量
short months[12];

// 声明并初始化数组
int yams[3] = {1, 3, 10};

// 数组列表初始化只能在声明数组的语句中进行
// 数组赋值给另一个数组是不允许的
int yams1[3] = {1, 2, 3};
int yams2[3];
yams2[3] = {1, 2, 3};		// 不允许
yams2 = yams1;				// 不允许

// 数组初始化不完全时其他未初始化的部分被初始化为默认值
int yams[5] = {1, 2};		// 数组被初始化为{1, 2, 0, 0, 0}

sizeof

int yams[3] = {1, 3, 10};
cout << sizeof yams << endl;			// 12,说明yams数组占12个字节
cout << sizeof yams[1] << endl;			// 4,说明yams[1]占4个字节

char name1[15];
cin >> name1; // 假设输出了java,四个字母
cout << sizeof name1 << endl; 			// 15, 说明name1数组占15个字节

char数组

数组名存储的是数组中第一个元素所在的地址

double wege[3] = {1, 2, 3};	// 则wege == &wege[0]

C风格字符串

// C风格字符串以'\0'结尾,它的ASCII码是0
char list1[]{'H', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '!'};
char list2[]{'H', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '!', '\0'};
// list1,list2都是char数组,但只有list2是一个C风格字符串

cout打印char数组的时候遇到空字符(也就是’\0’)后停止打印,所以如果cout<<list1; 可能会多打印其他东西

字符串常量(也叫字符串字面值)

char list1[11] = "Mr. Cheeps";

// 字符串常量在结尾处隐式的包含空字符,所以不用显示的表示,但空字符仍然占据一个字节,所以list1有11字节
// 所以,"S"和'S'不同,前者其实是两个字符组成的字符串。
// 实际上,"S"表示一个指向这个字符串的地址

字符串拼接

// C++中空格,回车,制表都被看做是一个空白
cout << "Hello, " "world" << endl;

cout << "Hello, "
    "world" << endl;

cout << "Hello,  world" << endl;

// 这三句话作用相同
// 字符串拼接时,前一个字符串末尾的'\0'会被后一个字符串的首个字符覆盖。

数组赋值给另一个数组是不允许的,所以对char数组进行操作有专门的的函数

strcpy() ,strncpy(),strcat() ,strlen() 和 strcmp()( 函数(位于头文件cstring里)

char name1[20] = "hello";
char name2[20];
string str="sir";
strcpy(name2, name1);				// strcpy(value2, value1)的作用是把数组value1里的内容赋值										给value2
strcat(name2, name1);				// strcat(value2, value1)的作用是把数组value1里的内容追加										到value2后边
strncpy(name2, name1, 19);			// 最后一个参数的意思是限制最多拷贝19个字符给name2,然后加空字									符。或者name1不足19个,那就把name1拷贝给name2,然后加空字符。
strcmp(name1, "hello")				// 如果值相同,返回0,如果第一个参数按字母顺序在第二个参数之										后,则>0,如果第一个参数在第二个参数之前,则<0
cout << name1 == "hello" << endl;	// 结果是0,因为比较的是地址而不是值
cout << sizeof name1 << endl;		// 20
cout << strlen(name1) << endl; 		// 5,不包含空字符。
cout << str.size() << endl;			// 3,这是获取string对象的长度的函数
									// strlen() 和 sizeof 不同,前者返回存储的字符数(不包含最后										的'\0'空字符),后者返回占据的字节数

getline() 和 get() 函数 ---- 面向行输入char数组

// 一次只能输入一个单词问题: char数组是以空字符串作为结尾的,但是用户输入的时候是不会输入空字符的,因为'\0'这个字符无法从键盘输入。所以cin把空白(空格,制表符,回车)作为一个字符串的结尾。

// getline() 和 get() 函数都是istream类提供的类成员函数

**getline()**函数:读取一行输入直到换行符出现,读取并丢弃换行符

char name[20];
cin.getline(name, 20);	 // 系统 接受到19个字符 或者 遇到换行符 的时候会停止读取。

**get()**函数,读取一行输入直到换行符出现,不读取换行符,而是将它留在输入流中cin也不会读换行符

char name[20];
cin.get(name, 20);		 // 系统遇到换行符就停止读取,所以如果只使用get()函数,系统始终不能跳过换行符
cin.get();				 // get()函数的变体,读取下一个字符,哪怕是换行符。
cin.get(name, 20).get(); // 作用和前两句加起来相同,读取字符串到换行符并丢弃换行符。

之所以可以出现cin.get(name, 20).get(); ,是因为cin.get(name, 20)返回一个cin对象,它仍可以调用istream类的成员函数get()

get() 相对 getline() 的优势是当出现输入问题时可以通过cin.get()获取下一个字符,从而得知输入流间断是由于达到数组长度上限还是遇到了换行符。

string类(在头文件string中,位于命名空间std里)

C++推出了string类存储字符串,string类不是字符数组,而是单独的把字符串也作为了一个数据类型

string比char数组更方便,更安全。

例如,不能把一个数组赋值给另一个数组,但可以把一个string对象赋值给另一个string对象

string s1 = "hello";
string s2;
s2 = s1;						// 允许把一个string对象赋值给另一个string对象
s2 = "world";					// 允许把C风格字符串赋值给string对象
s2 += s1;						// 允许string对象和其他string对象或者C风格字符串拼接。
cout << s2.size() << endl;		// 作用类似于char数组中的strlen()函数,获取字符串长度
// 并且可以看出,string类型变量是可以改变的。

cstring头文件是之前没有发明string类型的时候,对char数组进行操作的类

string头文件是发明了string类型之后,对string对象进行操作的类

// 根据 strlen(name1) 获取char数组name1的长度
// 根据 s1.size()		获取string对象s1的长度

可以看出,C函数通过参数指定操作对象,而C++函数通过对象名和句点运算符指定操作对象

getline()函数 ----面向行输入string对象

char name1[20];
cin.getline(name1, 20);				// 获取一行的输入并赋值给char数组name1,在遇到换行符或者已经从										输入流获取19个字符后将停止从输入流获取数据。
string str1;
getline(cin, str1);					// 获取一行的输入并赋值给string对象str,在遇到换行符之后停止从										输入流获取数据(因为string对象没有大小限制)

原始(raw)字符串

cout << R"("jeni" is a name of a wugui.\n)" << endl;
// 输出"jeni" is a name of a wugui.\n

此处的界定符是 “( 和 )” ,允许在情况需要时自动以界定符,可以在引号和括号中间添加任意数量的基本字符来自定义界定符,比如可以设置界定符为 “±/( 和 )±/”

结构struct

数组只能存同类型的数据,但结构可以存储多种类型的数据。结构还可以组成结构数组。

// 结构定义相当于生成了一个新类型
struct structName
{
    typeName1 name1;	// 每个列表项都是一条声明语句,被称为结构成员
    typeName2 name2;
    typeName3 name3;
    ...
};

// 创建这种类型的变量。
structName structname1;
structName structname2 = {value1, value2, value3...};

// 结构类型变量可以被作为参数,返回值,可以用一个结构变量给另一个同类型结构赋值
structName structname3 = structname2;

// 可以直接在定义结构的同时创建一个该结构类型的变量
struct structName
{
    typeName1 name1;
    typeName2 name2;
    typeName3 name3;
    ...
} structname1, structname2;		// 在定义结构structName的同时创造了两个结构变量structname1, 2

// 有时一个可能不需要专门定义一个结构,只是需要这个结构的一个变量,可以直接声明没有名称的结构类型
struct
{
    typeName1 name1;
    typeName2 name2;
    ...
} structname1;					// 定义一个结构变量structname1,它具有name1,name2等成员

成员运算符. (通过结构变量的变量名访问成员)

name.structname1;

结构数组

// 可以创建元素为结构的数组
structName structname1[size];
// 创建的时候可以进行初始化
structName structname2[size]
{
    {value1, value2, ...},
    {value1, value2, ...}
};

structname2;					// 代表刚刚创建的结构数组
structname2[index]; 			// 访问结构数组的某个结构元素
structname2[index].name1;		// 访问结构数组的某个结构元素的某个结构变量
// 

结构可以在函数外部声明以及内部声明,如果在函数外部定义结构,该结构定义语句之后的所有函数都可以使用此结构。

C++允许结构有成员函数

共用体union

// 共用体可以存储很多数据类型,但一次只能存其中的一个
union unionName
{
    typeName1 name1;
    typeName2 name2;
    typeName3 name3;
    ...
};
// 这些typeName对于union来说是它的形态之一,也就是说一个unionName型变量可能是一个typeName1类型的值,也可能是一个typeName2类型的值

// 一个共用体的应用:商品的id可能是数字也可能是字符串
struct widget
{
    char brand[20];
    int type;
    union id
    {
        long id_num;
        char id_char[20];
    } id_val;
};
...
widget prize;
if (prize.type == 1)
    cin >> prize.id_val.id_num;
else
    cin >> prize.id_val.id_char;

// 共用体也可以是匿名的
struct widget
{
    char brand[20];
    int type;
    union
    {
        long id_num;
        char id_char[20];
    };
};
...
widget prize;
if (prize.type == 1)
    cin >> prize.id_num;
else
    cin >> prize.id_char;

共用体的作用通常是节省内存

枚举enum

一种代替const创建符号常量的方式

// 以下这句话相当于定义了一个新类型enumName,这个类型的值可以是name1,name2...,没有赋值以外的其他运算
enum enumName = {name1, name2, ...};
// 此时 enumName 是一个枚举,name1, name2...称为枚举量
// 默认情况下,枚举量的值从0,1,2...递增,即在上边的语句里name1默认是0,name2默认是1...。
// 枚举量的值可以设置
enum enumName = {name1, name2 = value, name3...};
// 此时name1 = 0, name2 = value, name3 = value+1, 之后递增
// enumName 的取值范围是 2^n ~ 2^m,其中2^n是小于name1的最大数,2^m是大于最后一个枚举值的最小数

// 定义和初始化一个枚举变量
enumName enumname1 = name1;

// 枚举值是整形,所以可以把枚举值赋给int之类,但不允许把其他类型值直接赋值给枚举变量
int a = enumname1 + 1;						// 允许
enumName enumname2 = a - 1;					// 不允许
enumName enumname3 = enumName(a - 1);		// 允许
enumName enumname3 = enumname1 + enumname2;	// 不允许,因为枚举类型enumName没有定义+运算,所以这里默认会把enumname1 + enumname2转成int(enumname1) + int(enumname2),int型值不允许赋值给枚举变量

指针

指针类型的变量存的内容是值的地址,而不是值本身。通过**地址运算符&**获得一个变量的地址

面对对象编程和面对过程编程的区别:OOP强调在运行阶段做决策

编译阶段做决策:提前做好计划,无论发生什么都按照计划进行。在编译时给数组分配内存被称为静态联编

运行阶段做决策:根据当前情形进行决策,更灵活,比如可以在运行阶段决定数组的长度避免空间浪费。在运行时给数组分配内存被称为动态联编。动态联编的好处是可以在运行时确定数组的长度。动态联编创建的数组称为动态数组。动态数组用new关键字创建

int counts;
int* pcounts;
pcounts = &counts;
// &valueName 表示获取一个值的地址
// typeName* pname; 表示声明一个指向一个typeName类型值的指针变量,指针变量通常用p做前缀名
// pname 是一个指针, *pname 表示这个指针指向的值,pname的类型是typeName*,*pname的类型是typeName
// 上边的例子中,&counts = pcounts, counts = *pcounts

同时声明多个指针变量时容易出现的问题:

int* a, b;					// 这里其实声明了a是一个int指针型变量,b是一个int型变量
int *a, *b;					// 这其实才是声明两个int指针型变量 a 和 b

和数组一样,数组不是一个数据类型,int数组,char数组这种才算“一个数据类型”。

指针同样,指针不是一个数据类型,int指针,char指针这种才算“一个数据类型”。

可以用typedef关键字把int*声明成一种类型

typedef int* pint;
pint a, b;					// 这样,a和b都会被定义为int*变量

使用指针可能会导致危险:

int* a;
*a = 23;					// 表面看没有问题,实际上指针a没有进行初始化,所以它可能是任意值,它可能								指向程序代码,此时修改该指针处的值可能会导致不小心把程序代码修改了

通常来说修改指针指向内容的值之前,必须保证指针指向的位置是已知的。否则可能会导致问题。

指针在计算机中也是二进制存储,表现形式则是十六进制,但不能用十六进制数字给它赋值。

将指针变量加一后,其增加的值相当于增加了指向的类型占用的字节数

两个指针相减获得的也是其指向的类型变量的个数差

int a[2] = {4, 3};
int* pa = a;				// 此时pa指向数组a的第一个元素
pa++;						// 此时pa指向数组a的第二个元素,pa实际的值+sizeof int也就是加4
							// *(pa + 1) = pa[1];
int *pa2 = a[9];
cout << pa2 - pa;			// 得到的结果是a[9] - a[1] = 8

new关键字

一般来说变量名就是给一个内存命名,值就是这个内存里保存的值。指针允许不给一个内存命名就能直接访问到这个内存里的值。这种未命名的内存只能通过指针进行访问。C++中用new分配未命名内存。

int* a = new int;			// new会找到一个存int型变量的内存,把地址返回赋值给a

new分配的内存块和常规变量声明分配的内存块不同,常规变量的值(比方说int型变量,int*型变量)都存在里,而new(也叫自由存储区)里分配内存。

new声明的内存,在使用完之后需要用delete释放

delete的原理其实是删除new创建的内存,也就是说删除指针变量指向的内存,但不是删除指针变量

int* a = new int;
...
delete a;

new和delete必须成对使用,否则可能发生内存泄漏(堆里的内存空间耗尽)

另外,delete只能用于未命名的内存,如果一个内存有名字,不能用delete

int a = 10;
int* pa = &a;
delete pa;					// 这是不允许的

总之 new 和 delete 一定是成对出现的

用new关键字创建动态数组:

int* pname = new int[10];	// new关键字返回的是一个地址,int[10]告诉它应该找一个多大块的地址
delete [] pname;

箭头成员运算符->(通过地址访问成员)

用new关键字创建一个未命名的结构:

inflatable pa = new inflatable;	
// 这里的inflatable是某个结构的名字,此时pa是一个inflatable类型的指针

当一个结构类型变量访问它的成员时,使用成员运算符 ** ".**"

inflatable a;
a.name = "hello";			// 结构类型变量的变量名使用成员运算符访问成员

但问题是,用new关键字创建的结构未命名,C++设计了箭头成员运算符->”,可以允许那些只知道地址不知道名字的结构用它来访问成员

inflatable pa = new inflatable;	
pa->name = "hello";			// 结构地址使用箭头成员运算符访问成员

指针和数组的不同

  1. 指针类型的变量可以修改,但数组名是常量
int a[2] = {4, 3};
int* pa = a;
pa++;						// 正确的,指针变量值可修改
a++;						// 错误的,数组名是个常量

  1. sizeof 指针变量,得到的是指针变量的长度,sizeof 数组名,得到的是整个数组占据的长度
int a[2] = {4, 3};
int* pa = a;
cout << sizeof a << endl;	// 8,因为一个int型变量占4字节,a数组定义为有两个int变量的数组
cout << sizeof pa << endl;	// 4,因为存储一个int指针型变量要4字节

打印字符地址

cout遇到其他类型地址时会打印地址值,但遇到字符地址(char数组名,char指针,字符串常量)时就会打印地址指向的字符直到遇到空字符’\0’

所以如果想打印字符地址值,就把它强制转换成别的类型地址

char a[3] = {"hel"};
cout << a << endl;			// hel,因为cout遇到字符地址时会自动打印地址指向的字符串
cout << (int*)a << endl;	// 一个十六位进制数,因为cout遇到int型地址时会打印地址值

C++管理数据内存的方式

自动存储

函数中的常规变量使用自动存储空间,被称为自动变量,就是局部变量作用域就是它所在的代码块,保存在

静态存储

整个程序执行期间都存在的存储方式

两种方法定义静态存储的变量:1. 在函数外定义变量 2. 声明变量时使用关键字static

动态存储

C++中自由存储空间,和存储 自由变量 和 静态变量 的内存是分开的

动态存储的变量的生命无关函数或者程序,而是在new到delete期间都可用

数组、vector类、array类

头文件vector里有vector类,vector包含在命名空间std中

vector和string类似,string是动态的char数组,vector是动态数组,内置有new和delete

#include <vector>
using namespace std;
int main()
{
    vector<int> vi;
    vector<double> vd(n);				// vector是动态数组,长度可变,所以初始可以不用声明长度
    									// 在声明静态数组的时候要求声明的长度必须是常量,但是												声明vector数组的长度可以用常量也可以用变量
    
    return 0;
}

vector的优势是自由,缺点是慢,因为长度固定的数组比不固定的效率更高

头文件array里有array类,array包含在命名空间std中

array是静态的,长度固定

#include <array>
using namespace std;
int main()
{
    array<int, 5> ai;					// 声明一个长度为5的int数组ai
    array<double, 4> ad = {1.2, 3.3, 5.9, 0.2}
    									// 声明array数组的长度只能用常量
    return 0;
}

array相对于数组的优势是可以用赋值运算符 “=” 在array对象间赋值,而且array对象比数组更安全

vector 和 array 其实也可能发生越界访问错误,但可以选择避免

vector<int> a(2);
array<int, 2> b;
a[-1] = 10;					
b[200] = 1;								// 代码不会报错
a.at(1) = 1;							// vector 和 array 类都有 at() 函数,在不确定是否越界时											  可以使用at(),避免越界访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值