Skip to main content

复杂类型

info

《C++ Primer Plus》中文版第四章读书笔记

数组

数组存储多个相同数据类型值的复合结构类型,数组的声明语句为typeName arrayName[arraySize],例如int array[12],注意arraySize必须是整型常量或者const值,也可以是常量表达式(如8*sizeof(int)),即arraySize的值在编译时是已知的,数组的索引从0开始,编译器不会检查使用的索引是否有效
sizeof用于数组名得到的是整个数组中所有元素的字节数
只有在定义的时候才能进行数组初始化,此后就不能使用,也不能像普通变量一样把一个数组值赋给另一个数组:

int cards[4] = {1, 2, 3, 4};         //定义时初始化,正确
int hands[4];
hands[4] = {5, 6, 7 ,8}; //定义后初始化,错误
hands = cards; //不能直接赋值,错误

只对一部分元素进行初始化,编译器将剩下部分设置为0,利用这个特性可以构造全为0的数组:

int zero[100] = {0};

初始化时arraySize为空,编译器自动计算元素个数,利用这个特性不用去写元素个数:

int autonum[] = {1, 2, 3};

字符串

C++使用两个方式处理字符串:C风格和基于string类的方法。

C风格

C风格的字符串使用空字符'\0'结尾,其ASCII码为0

char correct[10] = {'c', 'o', 'r', 'r', 'e', 'c', 't', '\0'};
char wrong[10] = {'w', 'r', 'o', 'n', 'g'};

以上两个声明数组都是char数组,但是只有第一个是字符串,与字符串相关的函数都需要'\0'来判断是否到达字符串结尾,所以是否有'\0'至关重要,比如使用cout打印字符串的话,第二个会输出wrong加上一大串的其他字符
使用双引号括起字符串来省去写大量单引号的麻烦,这种字符串被称为字符串常量,同样可以省去大小让编译器自动计算,为了确保数组足够大,使用第二种方式显得更为安全,C++对字符串长度没有限制

char quote[10] = "haha";    //多出来的空间设置为0值,也就是空字符
char fish[] = "fish"; //省去数组大小

自己写数组长度时记得是你所看到的字符串字符个数加上1,另外一个是给空字符的,比如char test[5] = "test"中5不能写成4,否则就不是字符串而只是个char类型数组 区分单引号和双引号,"s"是字符串常量,等于's'+'\0',而's'是字符常量,并且"s"还表示字符串所在内存地址 写代码时利用任何两个由空白字符分隔的字符串常量都将自动拼接在一起切分代码,拼接时中间没有空格符,直接连在一起

cout<<"This is a very long string th"
"at I have to write it in tow lines.\n"

sizeof得到整个数组所占内存长度,strlen只计算从开始到第一个'\0'位置的内存长度 使用cin获取字符串的输入时,cin遇到空白字符就认为读取完毕,因此不能用cin来读取包含空白字符的字符串,使用cin.getline()或者cin.get(),两者都已换行符作为输入结尾标志,但是注意前者会把换行符从输入流中丢掉,而后者将换行符保留在输入流中,解决方法是用cin.get()读取一个字符来消耗掉这个换行符 混合输入字符串和数字的陷阱,对于分两行依次输入年份和地址的一种情况

cin>>year;              //错误,cin保留输入年份时的换行符,后面getline看到换行符赋值空字符串
(cin>>year).get(); //正确
cin.getline(address, 80);

string类简介

使用string类比使用数组更加简单,可以直接把一个string对象赋值给另一个对象,使用+操作符直接把两个string对象合并,使用+=把string对象追加到另一个对象末尾,读取一行输入到string对象getline(cin, str)

结构简介

声明结构类型的各个字段使用分号';'隔开,初始化结构体变量使用逗号','隔开各个字段的值,注意C++允许在声明结构体变量时省略关键字struct,可以同时完成结构体定义和创建变量甚至加上初始化

struct record {
int score;
char name[10];
} kayhaw = {
60
"kay haw"
};

不写结构体名,就是声明匿名的结构体,但以后就不能创建这类结构体变量了,和C的结构体不同,C++的结构体还可以有成员变量,结构体数组的声明和初始化,赋值类似普通数组,把基本类型变量赋值换成结构体赋值即可

record groupA[2] = {
{60, "kay haw"},
{61, "haw kay"}
};

结构体的位字段,字段类型应该为整型或者枚举类型,接下来是冒号':'加上指定使用的位数,使用没有名称的字段来提供间距,每个成员被称为位字段

struct torgle_register {
unsigned int SN: 4;
unsigned int: 4;
bool goodIn: 1;
bool goodTorgle: 1;
} tr = {14, true, false};

共用体

类型union类似struct,存储不同的数据结构类型,但是只能同时存储其中一种类型,共用体的长度为其最大成员的长度,共用体的用途之一是,当数据项使用多种格式但不会同时使用时,可节省空间,如商品id为整数,或者为字符串时

strcut gods {
char brand[20];
int type;
union id {
long id_num;
char id_char[20];
} id_val;
} glass;

对于上述结构体引用ID为glass.id_val.id_char,如果省略共用体类型名称,id_num和id_char被视为gods的两个成员,其地址相同,不需要中间标识符id_val,直接使用glass.id_num来引用商品ID

strcut gods {
char brand[20];
int type;
union {
long id_num;
char id_char[20];
};
} glass;

枚举

enmu提供另一种创建符号常量的方式,可以代替const,使用枚举类似于结构体,enmu colors {red, orange, yellow};这条语句声明新的枚举类型colors,将red,orange,yellow作为符号常量,对应整数值0-2,这些常量被称为枚举量,枚举类型只有赋值操作符,没有算术运算

colors pencil;
pencil = red; //正确
++pencil; //错误
pencil = red + yellow; //错误

枚举量是整型,可以被提升为int类型,但是int类型不能自动转换为枚举类型

int color = red;        //正确,枚举提升为整型
colors pencil = 3; //错误,整型不能转为枚举
color = 3 + red; //正确,枚举提升为整型

尽管枚举没有定义操作符+,但是在算术表达式中枚举被转换为整型,red+yellow被转为为0+2,但表达式类型为int,不能再赋值给枚举类型pencil。枚举常用来定义相关的符号常量用在switch语句中,不打算创建枚举类型变量时可以省略枚举名称enmu {red, orange, yellow};
默认情况下枚举定义中第一个枚举量的值为0,其他枚举量的值为其一个枚举量的值加1,也可以显式地定义值,枚举取值范围的上限由枚举量的最大值max确定,找到upper=2^n > max且n最小,则upper-1为枚举取值范围上限,下限由枚举量的最小值min确定,min>0下限为0,否则找到lower=-(2^m)且m最小,则lower+1为下限

指针和自由存储空间

指针的值是存储值的地址,使用*操作符解引用得到该地址的值,指针的声明格式为typeName * pointerName,'*'两边的空格是可选的,C++程序员偏向于int* p强调int是一种类型,即指向int的指针,传统上C程序员使用`int p`强调*p是int类型的值

注意,语句int *p1, p2;声明p1是int类型指针,而p2是int类型,对于每个指针变量都需要使用一个* {:.warning} 指针的初始化将地址值赋值给指针,一个好的习惯是将指针初始化为空指针NULL,或者确保指针指向的地址是有效的已分配的内存地址,否则使用解引用将得到未知的bug,不能简单地将整数赋值给指针`int pt = 0x88888888;,需要强制类型转换int pt = (int) 0x88888888 C++使用new操作符来分配内存并将内存地址赋值给指针,使用格式为typeName pointerName = new typeName,如int *p = new int,后面章节会讲到内存不够用引起的new失败的处理;分配内存使用完后还需要使用delete来释放内存归还给系统,delete p`不会删除指针p本身,你可以用它来指向另一个分配的内存块,new和delete一定要搭配使用,两次使用delete释放内存块的结果是不确定的,也不能使用delete释放声明变量所获得的内存

int *pt = new int;
delete pt;
delete pt; //错误,已经释放
int cat = 1;
int *pcat = &cat;
delete pcat; //错误,释放不是new申请的内存
  • 使用带上[]的new语句来申请动态数组,如int *arr = new int [10]相应地,释放该内存的delete也要带上[],即delete [] arr,使用动态数组访问元素和数组名方式相同
  • 指针具有指针算术的特性,但是将指针变量加1后,其增加的值等于指向类型占用的字节数,对于静态分配的数组名来说,sizeof的结果是数组元素大小乘以数组长度,对于动态分配的数组指针名来说,sizeof的结果是系统地址位长,数组名和指针名在使用方式上相同,都可以使用*解引用或者[]索引,但指针是变量,其值可以改变(++,--操作符),而数组名是常量,不可以变化
  • 在cout和多数C++表达式中,char数组名,指向char的指针和引号括起的字符串常量解释为字符串第一个字符的地址,一般给cout提供指针将会打印地址值,但是char类型指针将会打印字符串,使用(int )强制转换来打印地址值
  • 字符串的拷贝要先申请内存再使用strcpy或者strncpy来逐一复制,而不是直接使用'='操作符
  • new也可以用来申请复杂类型的内存,如结构体gods *toy = new gods;,此时使用->来访问成员字段,要想使用.来访问就必须先解引用,(*toy).brand.操作符的优先级大于*,因此需要带上括号
总结

自存存储:auto变量,在函数体{}里面定义的变量,分配在栈上,函数执行开始自动产生,函数执行结束自动销毁
静态存储:在函数体外定义的变量,或者声明时使用关键字static,在整个程序执行期间都存在
动态存储:生命周期不受程序或者函数限制,由程序员通过new和delete灵活控制