转载请注明出处
之前学过C++的内容,但是好久不看,又鉴于编程水准实在是惨不忍睹,因此对于自己的C++编程能力始终“耿耿于怀”,遂在前段时间入手一本《C++ Primer(第五版)中文版》,从头开始复习。但我使用的是蓝色封皮的版本,有些地方讲得不是很详细,遂在看的时候,在书上随手做了些标记,现将其整理出来,以作以后查询复习之用。
好了,闲话少说,进入正题。
稍微有些C++基础的同学会知道,在类的定义出现前,C++中的数据类型可以分为两种:简单类型和复合类型。
简单类型是诸如int,char,float,double等结构比较简单的数据格式。但是我们在处理一些复杂一些的问题时,仅仅依靠简单类型的变量定义是无法解决问题的,由此C++中又引入了一些结构稍微复杂一点的数据格式,这就是复合类型。而我们经常用到的数组、字符串、结构、枚举以及指针等都属于这一范畴。下面我们将分析这些复合类型的特点、用法及注意事项。
1. 数组
数组的声明与定义
数组是一种数据格式,能存储多个同类型的值(摘自《C++ Primer (第五版)中文版》)。数组是有长度的。创建一个新的数组可以使用声明语句,数组声明应指出一下三点:
(1) 数组的类型。如int、char、double等。
(2) 数组名。 关键字以外的字母或字母与数字或下划线的组合
(3) 数组长度
在C++中可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。例如:
int num[20]; //即是声明一个长度为20,数组名为num的整型数组。
数组的初始化
规则:只有在定义数组时才能使用初始化,并且不能将一个数组赋给另一个数组。
int stu[4] ={3,6,8,9}; //正确
int hand[4]; //正确
hand[4] ={2,4,6,8}; //错误
hand = stu; //错误
如果只对数组的一部分进行初始化,则编译器将其他部分设置为0(初始化数组比较好的方法)。
2. 字符串
字符串是存储在内存的连续字节中的一系列字符(摘自《C++ Primer (第五版)中文版》)。这意味着可以将字符串存储在char数组中。在C语言中,字符串与数组的转化具有特殊性质,请看下面两个声明:
char dog [5] = {‘b’,’e’,’a’,’u’,’x’}; //非字符串
char cat[5]={‘f’,’a’,’t’,’s’,’\0’}; //字符串
也可以用下面的声明方式:
char bird[10] = “Mr.Cheeps”; 引号括起的字符串隐式的包含结尾的空字符。
char fish[] = “Bubbles”;
字符串的长度
可以使用标准库函数strlen()来确定字符串的长度。strlen的参数只能是char*,并且必须是”\0”结尾的。例如:strlen(cat)。strlen()在头文件cstring (老式为string.h)中声明。与sizeof()的区别是,sizeof()获取的是数组占用的bytes,它的值只与在数组声明时的数组长度有关。
字符串的输入
面向行的输入1:cin.getline(数组名或指向数组的指针,读取输入的长度)。该函数在读取指定书目的字符或遇到换行符时停止读取,允许输入的字符串中含有空格。
面向行的输入2:cin.get(数组名或指向数组的指针,读取输入的长度)。与getline()的功能几乎完全一致,只是get()不再读取并丢弃换行符,而是将其留在输入队列中。这样的话只执行一次cin.get(name,size)不会报错,然而若程序未执行完毕,仍有cin.get(desert,size)操作,就会报错。如:
cin.get(name,size);
cin.get(desert,size);
由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符’\n’。导致get()认为已经到达行尾,而没有发现任何可读取的内容。
使用不带任何参数的cin.get()调用可读取当前输入队列中的下一个字符(即使是换行符)。我们平时使用cin>>读取变量时,回车键生成的换行符留在了输入队列中。然而cin流有一个默认状态是为输入操作符(即“>>"操作符)跳过空白,所以默认情况下用输入操作符读取时会跳过回车符等空白字符,实际上它是在缓冲区中的,可以通过noskipws操纵符让输入操作符不跳过空白字符,这样就可以读取到'\n'等空白字符了。因此我们可以连续使用cin>>而不用担心读取到空白会换行、回车符等。但是混合输入数字和面向行的字符串会导致问题。
3. String类
首先,要使用string类,必须在程序中包含头文件cstring(旧版本为string.h)。string类位于名称空间std中,因此在使用时必须提供一条using编译指令,或使用std::string来引用它。本人猜测:string头文件提供了string类的声明,std则提供了string类的实现方法。
string类的赋值、拼接和附加
stringstr1; //定义string 对象
string str2=”test”; //str2=”test”
str1=str2; // 将str2赋给str1。赋值后,str1=”test”
string str3=”name”;
str1=str2+str3; //str3的值加到str2的末尾,并将最后的结果赋给str1。str1=”testname”
使用到的函数
应使用strcpy_s(两个参数)、strcpy(两个参数)、strncpy(三个参数)和strncpy_s(三个参数),而不是使用赋值操作符“=”来将字符串赋给数组。
strcpy(charr1,charr2) ; //将字符串charr2复制到字符数组charr1中
strcat(charr1,charr2); //将字符串charr2附加到字符数组charr1中
str1.size(); //获取str1的长度
将字符数组内容传给字符串变量可以直接使用赋值操作符“=”:
字符串变量=数组名;
strcpy/strncpy/strcpy_s的用法详见下面的链接
http://blog.csdn.net/caomiao2006/article/details/4766416
string类的输入与输出
C++中,我们直接用cin>>str1命令,可以读取一个单词,遇到空白则停止读入。因此当我们要将一行读取到string对象中时,需要利用如下代码:
getline(cin,str1); //此getline()为string类的一个友元函数。
输出时可以直接使用cout<<str1。
4. 结构简介
结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据。
结构的定义和初始化:
Struct infat
{
char name[20];
float volume;
double price;
}; //分号不能少
上面的定义中,关键字struct表明,这些代码定义的是一个结构的布局。标识符infat是这种数据格式的名称。
infat inf; //inf即为一个infat类型的对象
结构的初始化可以在定义对象时进行,如:
infat inf = {
“Glaria ruiq”,
1.88,
29.99
}; //分号不能少
定义结构数组:
infat stu [100];
在定义时初始化:
infatguest[2] =
{
{ “Butawa”,0.5,21.99},
{“Godzilla”,2000,565.99},
};
5. 共用体
共用体(union)是一种数据格式,它能够存储不同类型的数据格式,但只能同时存储其中的一种类型。也就是说结构可以同时存储int、double和long,而共用体只能存储int、long或double。共用体的句法与结构相似,但含义不同。声明如下:
union one
{
intint_val;
longlong_val;
doubledouble_val;
}; //仍然有分号
可以使用one变量来存储int、long或double,条件是在不同的时间进行:
one pail;
pail.int_val= 15; //存储整型变量
cout<<pail.int_val;
pail.double_val= 1.38; //存储double变量,整型变量的值丢失
6. 枚举
C++的enum工具提供了另外一种创建符号常量的方式,这种方式可以代替const。
使用enum的句法如下:
enum spectrum{ zero,null=0,one,two=8,three};
spectrum 成为新类型的名称,被成为枚举。其中zero=0,null=0,one=1,two=8,three=9。zero在默认状态下被初始化为0,后面没有被初始化的枚举量的值将比前面的枚举量大1。在接下来的程序中,凡是出现zero被引用为int的地方,zero均被解析为0。
7. 指针和自由存储空间
声明和初始化指针
指针是一个变量,其存储的是值的地址,而不是值本身。指针是C++实现OPP特性的重要手段。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。指针名表示的是地址,*操作符被称为间接值或解除引用操作符,将其应用于指针,可以得到该地址处存储的值。指针声明如下:
int*p; //定义了一个指向int型变量的指针
int q=2;
int*w=&q; //在定义时初始化指针
p=&q; //令p指向q,此时*p=2
指针的危险
在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步骤,不能忽略。例如:
long *fellow; //定义一个long型指针
*fellow =2233;
fellow确实是一个指针,但是指向不明。上述代码没有将地址赋给fellow。我们不能确定2233会被放到哪里。由于fellow没有被初始化,它可能有任何值,不管值是什么,程序都将它解释为存储2233的地址。如果fellow的值碰巧为1200,计算机把数字存在地址1200上,而不管这个地址是否已被当前程序使用,这种错误会导致一些最隐匿、最难跟踪的bug。
切记:一定要在对指针应用解除引用操作符(*)之前,将指针初始化为一个确定的、适当的地址。
将数字值作为地址来使用,应通过强制类型转换将数字强制转换为适当的地址类型。
int *pt;
pt=(int*)0XB8000000; //赋值有效,pt内存储的地址为0XB8000000
使用new来分配内存
int*p=new int;
*p=1000;
char *q;
上面第一行代码是分配内存空间,此时p指向一个分配好的地址,该地址没有被初始化,所以里面的是随机值。第二行代码是将1000赋给p指向的地址中的变量。
切记:当使用strcpy(q,”strcpy”)将字符串”strcpy”拷贝到q之前,必须让q指针指向一个已分配好的内存地址。即是执行 q= new char [size] 语句。
程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456在常量区,p3在栈上。
static int c =0;全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
如果计算机没有足够的内存而无法满足new的请求。在这种情况下,new将返回0。可以通过检查new是否返回的是空指针,从而防止程序超界。
使用delete来释放内存
delete释放内存实质是解除指针与存储空间的指向关系。在程序中需要配对的使用new和delete,否则会发生内存泄露。
另外,不可重复释放已经释放的内存块,一般在简单的程序中比较容易发现这个错误。然而当应用比较复杂时就很难发现了。例如,在使用C++的类A时,假设类A中定义有指针变量char *p,且在构造函数中动态分配p指向的内存大小
(p=new char [10]),当我们用已存在的A的对象 a 初始化一个A的新对象 b时,若没有显式的定义复制构造函数(深度拷贝函数),那么程序的主体执行不会出什么问题,但是当程序执行完毕需要调用析构函数时,就会出现问题,因为需要析构两个对象,但是这两个对象中指针p指向的内存会被释放两次,因此会出错,甚至导致程序的终止。
还有delete不仅能释放用new释放的内存,还能释放空指针。
使用new来创建动态数组
声明语句如下:
int *psome = new int [10];
*psome=5; //psome[0]=5
psome指向数组的第一个元素,因此*psome是第一个元素的值。
使用指针访问数组有两种方式:
当使用int型数组和指针时:
第一:直接把指针名当数组名来使用。psome[2]是数组的第三个元素,&psome[2]是psome[2]在内存中的地址。
第二:使用*(psome+2)访问数组的第三个元素,(psome+2)即是第三个元素在内存中的地址。
注意一点,当使用psome=psome+sizeof(数据类型t)语句时,等价于:
int len =sizeof(psome指向的的数据类型 int) * sizeof(数据类型 t); //t为已定义的任意数据类型
psome+=len; //获得新的内存地址
还有一点,执行代码psome = psome +1;后,psome[0]是原数组的第二个元素。因为psome指向的初始位置发生了变化。
以上是使用数字型数组与指针时,遵守的规则。
当使用char数组和指针时:
charname[10] = “qweasd”;
char * p= name;
使用指针访问字符数组的方式:
Cout<<p[0]<<endl; //得到的值为‘q’,
cout<<p<<endl; //得到“qweasd”;
cout<<*P<<endl; //得到q
cout<<(int*)p<<endl; //得到p指向的数组的首地址,等价于printf("%X\n",(char *)p);
cout<<&p[2]<<endl; //得到“easd”
cout<<&name<<endl; //得到name的首地址
同时,使用*p[0]的形式是非法的。