C++入门第四章:复合类型
1 数组
数组(array)是一种数据格式,能够存储多个同类型的值。
使用数组前,首先要声明。声明包括三个方面:
- 存储每个元素中值的类型
- 数组名
- 数组中的元素个数
声明的通用风格如下:
typename arrayname[arrysize];
注;arrysize指定元素数目,必须是整型常量,不能是变量。
数组的很多用途均基于这样一个事实:可以单独访问数组元素。方法是使用下表或索引对元素进行编号。C++数组从0开始编号,并使用带索引的方括号表示法来指定数组元素。
注:编译器不会检查使用的下表是否有效,所以确保有效的下标值非常重要。
数组的初始化规则
只有在定义时才能使用,以后就不能用了,也不能将一个数组赋值给另一个数组。
int cards[4]={3,6,8,10};
初始化时提供的值可以少于元素数目。如果只对数组的一部分进行初始化,则编译器将其他元素设置为0。
如果初始化数组的时候方括号内为空,编译器将计算元素的个数。如:
short a[]={1,2,3,4};
编译器使a数组包含4个元素。
注:让编译器去计算数组大小是很糟糕的事情。
C++11数组初始化方法
首先,初始化时能够省略等于号。
int a[2]{1,2};
其次,不必在大括号内包含任何东西,这将把所有元素全部置0。
int b[10]={};
第三,列表初始化禁止缩窄。
long c={1.0,2.0,4} //不允许
2 字符串
字符串是存储在内存的连续字节中的一系列字符。
C++使用两种方法处理字符串。一种是C风格字符串,另种是基于string类的方法。
C-风格字符串以空字符作为字符串的结尾。
例:char c1={‘a‘,‘b‘,‘c‘,‘d‘,‘e‘,‘f‘}; //不是字符串
char c2={‘a‘,‘b‘,‘c‘,‘d‘,‘e‘,‘\0‘}; //字符串
cout打印字符串碰到\0后停止。
C++中另一种初始化字符串的方法:双引号。
char c3[11]="C++ language.";
char c4[]="C++ language.";
在这时,让编译器自己计算字符串长度显得更安全。
注意,字符串常量(双引号)和字符常量(单引号)不能够互换。
字符串的返回值是首字母的地址。
拼接字符串:cout<<"a" "b";
字符串的输入输出
cin使用空白(空格、制表符和换行符)来确定字符串的结束位置。这意味着cin每次仅能读取一个单词。
每次读取一行字符串输入
- 面向行的输入:getline()
getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。使用cin.getline()来调用此种方法。函数有两个参数:第一个参数用来存储输入行的数组名称,第二个参数为要读取的字符数。最多读取数为该数字减1(最后一个存储\0)。此函数在读取指定字符或者遇到换行符时自动停止读取。
例:cin.getline(name,20),最多包涵19个字符。
此函数自动忽略换行符。
- 面向行的输入:get()
基本用法与getline()相同,但是此函数不丢弃换行符。
例:cin.get(name,20)
cin.get()可自动读取下一字符。
另:类成员拼接:
cin.get(name1,20).get();
或:cin.getline(name1,20).getline(name2.20);
空行输入
get()遇到空行时会设置失效位使输入阻断。
使用cin.clear();可删除失效位,恢复输入。
和溢出会产生问题。
字母数字混合输入的问题:
cin>>year;
cin.get();
3 string类简介
C++98标准通过添加string类扩展了C++库,因此现在可以使用string类型的变量(C++中的对象)而不是字符数组来存储字符串。
要使用string类,必须使用名称空间std并且包含头文件string。
string的方式:
- 可以使用C语言风格来初始化string变量。
- 可以使用cin将键盘输入存储到string对象中。
- 可以使用cout来显示string对象。
- 可以使用数组表达式法来访问存储在string中的字符。
getline(cin,str);
string类将字符串声明为一个简单变量。
程序能够自动处理string的大小。
string类简化了字符串的合并操作。使用"+"可以实现字符串合并,使用"+="可实现在一字符串后添加字符串。
str1.size()返回字符串str1的长度,size()为一个类方法。
其他形式的字符串字面值
C++还有类型wchar_t,C++11新增了类型char16_t、char32_t。C++使用前缀L、u和U表示。
例:wchar_t a[]=L"abcd";
char16_t a[]=u"abcd";
char32_t a[]=U"abcd";
C++新增了一种字符串类型:原始字符串(raw)。在原始字符串中,字符表示的就是自己。例如序列\n表示的就是\和n而不是换行符。
例:cout<<R"ab "c",\n"<<endl; 显示为:ab "c",\n
cout<<R"+*(abc(abc)"a"\n.)*+"; 显示为:abc(abc)"a"\n.
即:使用R"";可在其中加\和双引号。使用R"+*()*+"可显示加\、双引号和括号。
-
结构简介
结构是一种比数组更灵活的数据格式,统一结构可以存储不同类型的数据。结构是C++OOP编程的基石。
结构定义:
struct structname
{
datatype1 name;
……..
}
结构声明与使用:
C语言形式:struct structname a
C++形式:structname a
C++声明结构变量时可以省略struct
变量声明在函数内,结构声明在函数外。
变量初始化的方式:
例:已定义了一个结构变量student(包含姓名、学号和姓名)。则可通过如下方式初始化:
student a={"Tom",123456,20}或
student a {"Tom",123456,20}
即由逗号分隔值列表,并将这些值用花括号括起。在程序中,每个值可以个占一行,也可以全部放在同一行。赋值符号可以省略。
初始化时大括号内可以为空,此时全置为0,如果有未初始化的成员,置为0。
最后,不允许缩窄变换。
其他结构属性
可以将结构作为参数传递给函数,也可以让函数返回一个结构。可将一个结构赋给另一个结构。
结构数组
结构体变量也可组成数组,例:(struct) student a[100];引用方式:a[1].name。
初始化方式:
student a[2]=
{
{"Tom",20110601,20},
{"Jack",20110602,20}
};
当中都要使用逗号分隔。
-
共用体
共用体是一种数据格式,可以存储不同的数据类型,但只能同时存储其中的一种类型。
例:union student
{ char name[20];
int number;
int age;
};
每个时刻共用体只能存储一个值。
匿名共用体:
例:struct student
{ char name[20];
union
{ int id1;
int id2;
};
}a;
可使用a.id1引用结构体中的id1。
-
枚举
enum工具提供了另一种创建符号常量的方式。使用方法与结构体类似。
例:enum day {Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};其中,Sunday=0,Monday=1,以此类推。
上面定义的Sunday等是符号常量,又被称为枚举量。
枚举量有一些特殊属性:
- 在不进行强制类型转换的时候,只能将定义枚举时使用的枚举量赋给这种枚举的变量。例:
enum day;
day a;
a=Sunday; //正确
a=0; //不正确,非枚举量
- 可通过强制类型转换进行赋值。例:
a=day(1);但是a=day(10)是错误的。
若只是使用枚举量创建常量,可直接使用以下语句定义:enum {a,b,c,d};省略枚举类型的名称。
设置枚举量的值
enum bit{one=1,two=2,four=4};
其中,指定的值必须为整数。
enum num{a=1,b,c=9,d}; b=2,d=10;
枚举值的取值范围
取值范围的定义:首先找出枚举值中最大的数。在2的幂中,找到比这个数大的最小值,然后减1即为上限。(如:枚举中的最大值为101,则上限为2^7-1=127)。如果最小值不小于零,下限为0,否则,与上限同理。
-
指针和自由存储空间
指针是一个变量,其存储的是值的地址,而不是值本身。
对于常规变量,应用地址运算符(&),就可以获得它的位置。比如home为一变量,&home,就是变量的地址。
使用常规变量时,值是指定的量,地址为派生量。而指针将地址视为指定的量,而将值视为派生量。指针名表示的是地址,*运算符被称为间接值或解除引用运算符,将其运用于指针,可以得到该地址处存储的值。
一般计算机使用16进制表述指针,也有十进制的。
指针的声明
例:int *p_updates;其中,p_updates为地址。
传统C语言初始化方式:int *p;
C++方式:int* p;表示int* 为一种指向int类型的指针。
甚至也可以这样:int*p;
注:对每个指针变量名,都必须使用*,即int* p1,p2;其中p2为一个int型变量。
指针的危险
在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步骤。
例:long *a;*a=233333;
上述代码并没有将地址赋给a,233333将放在哪里呢?我们不知道,a指向的地方并不是233333的地址。所以一定要在指针使用*之前初始化它。
指针和数字
指针不是整型,但是计算机通常把指针当做整型变量来存储。指针不能进行乘的运算,不能将整数赋给指针。如:
int *p;p=0xB8000000;
但是可以通过强制类型转换赋值。即:
int *p;p=(int *) 0xB8000000;
使用new来分配内存
int *pnew=new int;
new运算符实现了在运行阶段为int值分配未命名的存储空间,并通过指针来访问这个值。
如果使用的是:int a;int *pt=&a;则可使用a来访问该值,而使用new只能够使用指针访问该值。
我们称pnew指向的是一个"数据对象"。处理数据对象的指针方法可能不一定好用,但是这在管理内存方面有更大的控制权。
为一个数据对象(基本类型或者结构)获得并指定分配内存的方法:
typename *pointer_name=new typeName;
使用举例:
int *pn=new int;
*pn=1001;
使用delete释放内存
使用delete释放new分配的内存。
例:int *pn=new int;
delete pn;
但是这并不是意味着删除指针pn本身,删除的只是pn指向的内存块而已。
new和delete应该成对使用,否则会出现内存泄露的情况。
使用new来创建动态数组
使用声明来创建一个数组,在编译时给它分配内存空间,属于静态联编,使用new来创建数组,在运行时分配空间,属于动态联编。
例:int *pn=new int [10]; //创建动态数组
delete [] pn; //删除动态数组
上面的pn指向数组中的第一个值。
即:
type_name *pointor_name=new type_name [num_elem];
其中,num_elem可以是整型常量或者变量。
使用动态数组的方法:
以上面的pn为例,可使用pn[0]……pn[9]访问数组元素。也可使用*(pn+1)进行访问。
-
指针、数组和指针算数
指针和数组基本等价的原因在于指针算术和C++内部处理的方式。C++将数组名作为指针,且为常量。
将指针变量加1后,增加的值等于指向类型占用的字节数。
注:数组名代表数组第一个值的地址,&数组名代表整个数组的地址。
指针和字符串
例:char a[20]="name";
我们可以认为a是字符串第一字符‘n‘的地址。如果使用cout输出的话,从‘n‘开始输出,知道碰到\0结束。
注:在cout和多数C++表达式中,char 数组名、char 指针以及使用引号括起来的字符串常量都被解释为字符串第一个字符的地址。
字符串的字面值是常量。
注:在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名、也可以是使用new初始化过的指针。但不可使用字符串常量和为被初始化过的指针来接受输入。
一般来说,如果给cout提供一个指针,他将打印指针。但如果指针的类型为char *,cout将显示字符串,如果要显示地址,必须使用强制类型转换,如:(int *)字符指针。
例:char a[20]="an animal";
char *ps=new char[strlen(a)+1];
strcpy(ps,a);
注:上面的程序中不能使用ps=a;否则ps分配的内存将无法使用。
另:strcpy(a,b);//a:目标地址,b:源地址
如果b的长度比a定义的长,则会继续复制。
strncpy(a,b,n);//a:目标地址,b:源地址,n:长度
使用new创建动态结构
首先定义一个结构
struct student
{
char name[10];
int num;
int age;
};
student *ps=new student;
此时,要使用"->"运算符引用结构中的元素。(也可以使用(*ps).num引用)。
自动存储、静态存储和动态存储
根据分配内存的方式,C++有三种管理数据内存的方式:自动存储、静态存储和动态存储。C++11新增了一种:线程存储。
- 自动存储
函数内部定义的常规变量使用自动存储空间,被称为自动变量。函数调用时产生,函数结束时消亡。
自动变量为一局部变量,作用域为代码块(花括号中的一段代码)。
自动变量存储在栈中。
- 静态存储
整个程序执行期间都存在的变量,使用static声明。
3,动态存储
new和delete提供的方式。
-
类型组合
本章介绍的数据类型可以组合
struct student
{ char name[20];
int num;
int age;
}
现在声明一个变量:
student Tom;
可使用Tom.age访问结构中的成员。
若 student * ptom=&Tom;
则使用ptom->age访问。
若声明一个数组:student s[3];
s[0].age 访问成员
(s+1)->age 访问其成员
同时可创建指针数组:
const student *pp[3]={&s[1],&s[2],&s[3]};
访问成员方法:
std::cout<<pp[1]->age<<std::endl;
可创建指向指针的指针:
const student **ppp=pp;
C++11中也可写成:const student ppp=pp;
-
数组的替代品
-
模板类vector
模板类vector类似于string类,也是一种动态数组。可在运行阶段设置vector的长度,可在末尾附加新数据。
- 使用模板应包含头文件vector
- vector包含在名称空间std中
- 模板使用不同的语法来指出存储的数据类型
- vector使用不同的语法来指定元素数
例:#include<vector>
using namespace std;
vector<int> vi;
int n;
cin>>n;
vector<double> vd(n);
声明vector对象的方法:
vector<typename> vt(n_elem);
其中n_elem可以是整型常量或者变量,省略则默认为0。
2 模板类array
C++11中提供的新类型,固定长度的数组,但是比普通数组更方便和安全。
例:
#include<array>
using namespace std;
array<int,5> ai;
array<double,4> ad={1.1,1.2,1.3,1.4,1.5};
声明方式:
array<typename,n_elem> arr;
其中n_elem必须是整型常量
总结
array和数组存放在栈中,vector存储在自由存储区或者堆中。
array对象可以整个赋值。
例:
array<double,4> a1={1.1,1.2,1.3,1.4,1.5};
array<double,4> a2;
a2=a1;是合法的。
a1[-2]=199;是合法的。
即*(a1-2)=199;
但是会产生意想不到的错误。
vector和array避免此类错误的方法:
a1.at(1);
如果它捕获非法索引,则中断程序。
总结
数组、结构和指针是C++中的3种复合类型。
数组可以在一个数据对象中存储多个同类型的数据。通过索引和下标访问。
结构可将多个不同类型的值存放在同一数据对象中。使用成员关系运算符(.)来访问其中的成员。
共用体只能存储一个值。
指针使用来存储地址的变量。new运算符允许程序运行时为数据对象提供内存,返回获得的内存地址。
C++98新增的标准模板库(STL)提供了模板类vector。