在《C++学习笔记(3)——C++基本类型》中总结了基本类型即整型和浮点型,但是对于一个稍微复杂点的程序,这些基本类型往往是不够的,或者准确的说是不方便的。本篇笔记总结下c++的复合类型。
一、C++内置数据类型分类
C++的内置数据类型包括基本类型和复合类型,基本类型也即我们平时常见的整型和浮点型;复合类型包括数组、结构体和指针等。
二、数组
数组存在的意义很显然了:加入你要表示100个人的身高,你不可能创建100个整型变量来表示。
1.声明一个数组
一维数组:typeName arrayName[arraySize]
如int height[100];
多维数组: typeName arrayName[arraySize1][arraySize2][arraySize3]
你可能常见二维数组。如int height[100][10];其实单从语法上来说维度是没有上限的,但是从实际意义出发,四维已经没什么用了。
以上声明中的arraySize必须是整型常量,或者更准确的说是unsigned int,而且不能是变量。
在使用的时候直接用下标索引值就好,比如height[2].要注意,索引值是从0开始的,到arraySize-1。
2.初始化数组
一种是在声明的时候直接初始化,用花括号直接将字面值列出来,这被叫做列表初始化(这在C++11中应用很广泛)。比如:
int aa[4] = {1,3,5,7};
这里可以不用指定aa的大小,C++会自动计算列表中数据的长度:
int aa[] = {1,3,5,7};
但是不允许诸如基本型声明和初始化那样:
int aa[4];
aa[4] = {1,3,5,7};//这是错误的
另外,如果只想给数组初始化部分值,可以
int aa[4] = {0};//将所有元素都初始化为0,这是很好的一种初始化方式。
int aa[4] = {0,1};//将aa[1]初始化为1,其他元素自动初始化为0。
也就是说编译器会自动把没有其余元素自动初始化为0.
另一种是用下标初始化:
int aa[4];
aa[0] = 1;
aa[1] = 3;
aa[2] = 5;
aa[3] = 7;
如果字面值有一定规律,当然可以用循环搞定:
for(uint i=0; i<arraySize; i++)
{
aa[i] = 2*i-1;
}
这里用的unit是我自定义的unsigned int类型,因为数组下标最好用非负值,虽然程序不会出错,但是一些编程规范或标准里会如此要求。
3.特殊的数组——字符串
字符串是存储在内存中的一段连续字符,C++提供了以下两种类型的字符串表示形式:C 风格字符串和C++ 引入的 string 类类型。
a) C 风格字符串
C 风格字符串实际上一个char数组,且以‘\0’结束,也即:
char hello[6] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
如果没有’\0’将不被认为是字符串,也就是说编译器会一直会找到‘\0’,才会认为是字符串结尾。因此,在初始化一个空的字符串时,请以‘\0’填满它。
上面的初始化方式太不友好了,我们常见的是:
char hello[6] = “hello”;
用双引号表示一个字符串,而且不用标明’\0’,编译器会自动添加。也就是说双引号作为类似于关键字的符号被编译器认识并处理,注意是“双引号”,不是“单引号”。在上一篇笔记中总结过了,单引号表示字符(ASCII码)。其实,这个双引号括起来的字符串表示的是该字符串的首地址,后文的指针部分将再介绍。
注意上述数组的大小是6,也就是得算上‘\0’。strlen(hello)函数计算的是5,也即不算’\0’。所以要特别注意存放一个“hello”,需要制定的数组大小为strlen(“hello”)+1。当然就像其他数组一样,也可以让编译器去判断长度:
char hello[] = “hello”;
通常来讲,对于C 风格字符串使用,经常会用到strcpy、strcat等函数:
strcpy(s1, s2);//复制字符串s2到字符串s1。
strcat(s1, s2);//连接字符串s2到字符串s1的末尾。
strlen(s1);//返回字符串s1的长度。
strcmp(s1, s2);//如果s1和s2是相同的,则返回0;如果s1<s2则返回值小于0;如果s1>s2则返回值大于0。
strchr(s1, ch);//返回一个指针,指向字符串s1中字符ch的第一次出现的位置。
strstr(s1, s2);//返回一个指针,指向字符串s1中字符串s2的第一次出现的位置。
b)C++的string类
C++可以用string类型来定义和操作字符串。因为我们不用关心字符串的长度信息,而且该类中定义了很多好用的成员函数,使得string类比C风格字符串要好用的多。
我们知道两个数组间赋值得用strcpy函数,数组拼接得通过strcat函数。而且调用这些函数时总会面临数组长度的问题,改不好就越界了。但string类让此类操作十分简单:而且不用你考虑长度
string str1 = “hello”;
string str2 = “world”;
string str3 = str1;
string str4 = str3 + str2;
str4 += str4;
string类具有丰富实用的接口函数可供调用:
a) =,assign() //赋以新值
b) swap() //交换两个字符串的内容
c) +=,append(),push_back() //在尾部添加字符
d) insert() //插入字符
e) erase() //删除字符
f) clear() //删除全部字符
g) replace() //替换字符
h) + //串联字符串
i) ==,!=,<,<=,>,>=,compare() //比较字符串
j) size(),length() //返回字符数量
k) max_size() //返回字符的可能最大个数
l) empty() //判断字符串是否为空
m) capacity() //返回重新分配之前的字符容量
n) reserve() //保留一定量内存以容纳一定数量的字符
o) [ ], at() //存取单一字符
p) >>,getline() //从stream读取某值
q) << //将谋值写入stream
r) copy() //将某值赋值为一个C_string
s) c_str() //将内容以C_string返回
t) data() //将内容以字符数组形式返回
u) substr() //返回某个子字符串
v)查找函数
w)begin() end() //提供类似STL的迭代器支持
x) rbegin() rend() //逆向迭代器
y) get_allocator() //返回配置器
这些函数的具体用法可以在今后的笔记中详细描述。
6.模板类——vector和array
C++还有两个模板类,可以替代数组,完成数组的功能。这里做简单的介绍:
这两类模板类时vector和array(C++11才有的),因为是模板类,所以其功能类似于string,也就是说它也具有丰富的接口函数可供调用。
用vector声明一个数据:
vector<int> height(100); //int height[100];
其中的“100”可以是变量,所以它是动态创建的:
int n = 100;
vector<int> height(n);
用array声明一个数组:
array<int 100> height;// int height[100];
此处的100是常量,因此是静态创建的。
不论是vector还是array,都可以用数组下标的方式访问其元素值。如height[1]。vector的功能比较强大,但是他却像用指针动态生成的数组一样,创建在自由存储区或堆上。而array是创建在栈上,这意味着array的处理效率比vector高。
他们的具体接口将在专门的笔记中总结。
三、结构体
结构体的意义也是显而易见的:描述一个人的身高,年龄,性别等不同基本类型的数据类型,将不同的类型归类为一个新的自定义类型。这和C++的类有相似之处,或者说是类的基石。
1.结构体的声明
声明方式:
struct PerInfo
{
int age;
int hegit;
int sex;
};
使用时只要创建一个结构体的对象即可,这看上去很像int a;的形式。
PerInfo John;
在一些跟硬件关联比较紧密的平台,通常要获取位字段的信息,结构体为这种形式提供了很好的方式:位字段的声明方式:用冒号显示所占的位,注意可能会有字节自动对齐的问题。
struct UDPHeader
{
double aa;
unsigned int len:4;
unsigned int id:4;
int time:8;
unsigned int success:1;
};
(注)对齐方式:
在没有#pragma pack宏的情况下,成员对齐有一个重要的条件,即每个成员按自己的方式对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。结构体的总大小,为其成员中所含最大类型的整数倍。
如上述结构体aa占了8个字节,而后续的int一共才占了11位,不到2个字节,但用sizeof(UDPHeader)求得大小为16字节,也就是后面向aa看齐,补齐了8个字节。
2.使用结构体
使用结构体,包括赋值和取值,都用“.”:
John.age = 25;
int height = John.height;
初始化也可以用列表初始化:
PerInfo John =
{
25,
170,
1//用1表示男性
};
4.共用体union
union是结构体的一种特例。有时需要使几种不同类型的变量存放在同一段内存单元中。虽然在同一段内存中不同数据类型所占字节数不同,但都从同一地址开始存放,也就是使用了覆盖技术,几个变量互相覆盖。这种使几个不同的变量共占一段内存的结构,称为共同体类型的结构(也称为联合类型)。
union data
{
int i;
char ch;
double d;
}a,b,c;
结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。共用体变量所占的内存长度等于最长的成员的长度。所以,共用体常被用来节约内存空间。上述union的长度为double的长度8字节。
使用共用体变量的目的是希望在同一个内存段存放几种不同类型的数据。注意:在每一瞬时只能存放其中一种,而不是同时存放多种。换句话说,每一瞬时只有一个成员起作用,其他成员不起作用。
能够访问的是共用体变量中最后一次被赋值的成员,在对一个新的成员赋值后原有的成员就失去作用。因此在引用共用体变量时应注意当前在共用体变量中起作用的是哪个成员。
共同体变量的地址和它的各成员的地址相同,为同一个地址。
不能对共用体变量名赋值;不能引用变量名来得到一个值;不能在定义共用体变量时对它初始化;不能用共用体变量名作为函数参数。
也就是不能cout<<a,只能cout<<a.i。
5.枚举enum
enum是结构体的一种特例。枚举提供了另一种创建符号常量的方式。它是由用户定义的若干枚举常量的集合。
enum 类型名 {<枚举常量表>};
关键字enum——指明其后的标识符是一个枚举类型的名字。
枚举常量表——由枚举常量构成。“枚举常量”或称“枚举成员”,是以标识符形式表示的整型量,表示枚举类型的取值。枚举常量表列出枚举类型的所有取值,各枚举常量之间以“,”间隔,且必须各不相同。取值类型与条件表达式相同。
枚举常量代表该枚举类型的变量可能取的值,编译系统为每个枚举常量指定一个整数值,缺省状态下,这个整数就是所列举元素的序号,序号从0开始。
可以在定义枚举类型时为部分或全部枚举常量指定整数值,在指定值之前的枚举常量仍按缺省方式取值,而指定值之后的枚举常量按依次加1的原则取值。如:
enum week {Sun=7, Mon=1, Tue, Wed, Thu, Fri, Sat};//枚举常量Sun,Mon,Tue,Wed,Thu,Fri,Sat的值分别为7、1、2、3、4、5、6。
枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。例如,以下定义非法:
enum letter_set {'a','d','F','s','T'}; //枚举常量不能是字符常量
enum year_set{2000,2001,2002,2003,2004,2005}; //枚举常量不能是整型常量。
枚举变量的值只能取枚举常量表中所列的值,就是整型数的一个子集。枚举变量只能参与赋值和关系运算以及输出操作,参与运算时用其本身的整数值。例如,设有定义:
enum color_set1 {RED, BLUE, WHITE, BLACK} color1, color2;
enum color_set2 { GREEN, RED, YELLOW, WHITE} color3, color4;
则允许的赋值操作如下:
color3=RED; //将枚举常量值赋给枚举变量
color4=color3; //相同类型的枚举变量赋值,color4的值为RED
int i=color3; //将枚举变量赋给整型变量,i的值为1
不能直接将常量赋给枚举变量。如: color1=1; //非法
不同类型的枚举变量之间不能相互赋值。如: color1=color3; //非法
枚举变量的输入输出一般都采用switch语句将其转换为字符或字符串;枚举类型数据的其他处理也往往应用switch语句,以保证程序的合法性和可读性。