我们将讨论三种数据类型:首先是数组,然后是指针(一种更加重要和抽象的数据类型),最后是结构(它的用法和用途可以让大家对面向对象的编程技术有基本的印象)
1.数组
数组的优点在于,一个数组可以把许多个同类型的值存储在同一个变量名下。注意:我们不会把不同数据类型的数据混杂保存在同一个数组中,就像猫和狗搞在一起会出事一样的道理。
编程任务:定义一个数组容纳10个整数,这些整数来自用户输入。我们将计算这些值的累加和、平均值并输出。
#include <iostream> int main() { const unsigned short ITEM = 10; int num[ITEM]; std::cout << "请输入" << ITEM << "个整型数据! "; for( int i=0; i < ITEM; i++ ) { std::cout << "请输入第" << i+1 << "个数据: "; while( !(std::cin >> num[i]) )//对输入cin进行检验,如果出错,返回0,重新输入 { std::cin.clear();//清空输入缓冲区 std::cin.ignore(100, ' '); std::cout << "请输入一个合法的值"; } } int total = 0; for( int j=0; j < ITEM; j++ ) { total += num[j]; } std::cout << "总和是: " << total << " "; std::cout << "平均值是: " << (float)total / ITEM; std::cout << " "; return 0; }
在C 语言里,字符串被实际存储在一个字符数组中,如char s[20]。我们在C++ 中我们也可以用同样的方法实现,但C++ 提供了更好的std::string 类型,所以我们不必再使用老式的C 方法咯。例如:
#include <iostream> #include <string> int main() { std::string str; std::cout << "请随便输入一个字符串: "; //std::cin>>str;//遇到空格结束接受字符 std::getline(std::cin, str);//接收一行字符 std::cout << str << " "; return 0; }
2.指针
注意:C++的对齐:在C++里,变量类型是根据它们的自然边界进行对齐的,编译器自动帮我们处理对齐(对齐问题会因为系统平台的不同而不同)。例如 int a =12; char b = M; float c = 3.14;浮点型C的值是从内存地址8处开始,而不是从内存地址5处存储。
内存对齐是编译器为了便于CPU快速访问而采用的一项技术,我们先从一个例子开始,对下面的类(或者结构体):
class node { char c; int i; short s; }no;
sizeof(no)的值是多少呢,如果你的回答是7(1+4+2),那么你应该认真阅读下面的内容。可以在编译器上试试,输出的结果是12,这就是内存对齐的结果。
为什么要进行内存对齐呢?
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
在创建指针时,空格放在哪里都是没关系的,下边的语句都是可以接受的:
int *p1; int * p1; int* p1;
另外允许void类型的指针变量:void *p; 这里指针只用来存放一个地址,地址都是4个字节的,没有具体说明指针指向的数据的类型,可以到具体应用时具体来分析。
温故而知新:
- 创建变量时,系统将分配一些内存块用来保存它们的值;
- 每个内存块拥有一个独一无二的地址;
- 变量的地址可以用 &variablename 语法来取得;(注:& 我们称为 ”取地址” 操作符)
- 可以把地址赋值给一种称为指针的特殊变量;
- 指针的类型必须与由它保存其地址的变量的类型一致。
接下来给大家介绍点真正的好东西:
int a = 456; char b = ‘C’;//C/C++中单引号表示字符,双引号则是字符串,在内存中存放的是asc码 int *aPointer = &a; Char *bPointer = &b;
这会让程序保留4个内存块,两个为变量保留,两个为指针保留。变量a 和变量b 里边存放的是变量的值; 两个指针变量存放着指针的值,这些值是其他变量的地址。如下图:
当我们知道了某个变量在内存中的地址(通过指针),就可以利用指针访问位于该地址的数据。 这需要对指针进行 ”解引用(Deference)”处理:即在指针名的前面加上一个星号(*)。 如: std::cout << *aPointer; 这里我们来理解一下:把整数变量 a 的地址存储在 aPointer 指针里之后,*aPointer 和变量 a 将代表同一个值。 因此: *aPointer = 123; 将会:
//实例 #include <iostream> int main() { int a = 123; float b = 3.14; char c = 'C'; unsigned long d = 19880808; std::string e = "I love FishC!"; std::cout << " a 的值是: " << a << " "; std::cout << " b 的值是: " << b << " "; std::cout << " c 的值是: " << c << " "; std::cout << " d 的值是: " << d << " "; std::cout << " e 的值是: " << e << " "; //C语言的所有声明都放在最前边,C++可以放到中间等处 int *aPointer = &a; float *bPointer = &b; char *cPointer = &c; unsigned long *dPointer = &d; std::string *ePointer = &e; *aPointer = 456; *bPointer = 4.13; *cPointer = 'F'; *dPointer = 20111124; *ePointer = "I love Beauty!"; std::cout << " a 的值是: " << a << " "; std::cout << " b 的值是: " << b << " "; std::cout << " c 的值是: " << c << " "; std::cout << " d 的值是: " << d << " "; std::cout << " e 的值是: " << e << " "; return 0; }
1. 一定要牢记的事实:指针所保存的是内存中的一个地址。它并不保存指向的数据的值本身。因此,务必确保指针对应一个已经存在的变量或者一块已经分配了的内存。
2. 星号有两种用途,时常困惑了初学者:
第一种是用于创建指针: [ex] int *myPointer = &myInt;
第二种是对指针进行解引用: [ex] *myPointer = 3998;
3. C++ 允许指针群 P,就是多个指针有同样的值 int *p1 = &myInt; int *p2 = &myInt;
4. C++ 支持无类型(void)指针,就是没有被声明为某种特定类型的指针,例如: void *vPointer; 注意:对一个无类型指针进行解引用前,必须先把它转换为一种适当的数据类型。
计算机把数组是以一组连续的内存块保存的,例如:int myArray[3] = {1, 2, 3}; 在内存中是类似于这种形式存储:
这就说明了数组拥有很多个地址,每个地址对应着一个元素。可能你会觉得要用指针指向一个数组,需要用到很多指针变量? 其实在C/C++中,事实远没有想象那么困难。数组的名字其实也是一个指针(指向数组的基地址,就是第一个元素的地址)。 就刚才的例子,以下两句做同样的事情:
int *ptr1 = &myArray[0]; int *ptr2 = myArray;
我们轻易的将数组的基地址用指针变量保存起来,那我们接着讨论第二个问题:如果我想要通过指针访问其他数组元素,应该怎么办?
试试:ptr1++;
指针运算的奇妙之处就在于,以上并不将地址值简单+1处理,它是按照指向的数组的数据类型来递增的,也就是 +sizeof(int)。
我们用实例来演示一下这个特性:example
#include <iostream> int main() { const unsigned short ITEMS = 5; int intArray[ITEMS] = {1, 2, 3, 4, 5}; char charArray[ITEMS] = {'F', 'i', 's', 'h', 'C'}; int *intPtr = intArray; char *charPtr = charArray; std::cout << "整型数组输出: " << ' '; for( int i=0; i < ITEMS; i++ ) { std::cout << *intPtr << " at " << reinterpret_cast<unsigned long>(intPtr) << ' '; intPtr++; } //reinterpret_cast是C++里的强制类型转换符。 std::cout << "字符型数组输出: " << ' '; for( int i=0; i < ITEMS; i++ ) { std::cout << *charPtr << " at " << reinterpret_cast<unsigned long>(charPtr) << ' '; charPtr++; } return 0; } /* 结果: 整型数组输出: 1 at 2686736 2 at 2686740 3 at 2686744 4 at 2686748 5 at 2686752 字符型数组输出: F at 2686720 i at 2686721 s at 2686722 h at 2686723 C at 2686724 */
数组可以是任何一种数据类型,这意味着我们完全可以创建一个以指针为元素的数组。
注意:
int Array[5] = {3, 4, 5, 6, 7};
int *ptr = Array;
则 *ptr + 1; *(ptr + 1); 两者有何区别?
#include <iostream> int main() { int Array[5] = {3, 4, 5, 6, 7}; int *ptr = Array; std::cout<<"*ptr+1: "<<*ptr+1<<std::endl;//结果为4 std::cout<<"*(ptr + 1): "<<*(ptr + 1)<<std::endl; //结果为4 return 0; }
数组和指针的应用举例:
1. 重载
#include <iostream> void print( int *pBegin, int *pEnd ) { while( pBegin != pEnd ) { std::cout << *pBegin; ++pBegin; } } void print( char *pBegin, char *pEnd ) { while( pBegin != pEnd ) { std::cout << *pBegin; ++pBegin; } } int main() { int num[5] = { 0, 1, 2, 3, 4 }; char name[5] = { 'F', 'i', 's', 'h', 'C' }; print( num, num+5 ); std::cout << ' '; print( name, name+5 ); std::cout << ' '; return 0; }
对于上边的例子使用模板进行修改:泛型程序设计
2. 泛型程序设计
#include <iostream> template <typename elemType> void print( elemType *pBegin, elemType *pEnd ) { while( pBegin != pEnd ) { std::cout << *pBegin; ++pBegin; } } int main() { int num[5] = { 0, 1, 2, 3, 4 }; char name[5] = { 'F', 'i', 's', 'h', 'C' }; print( num, num+5 ); std::cout << ' '; print( name, name+5 ); std::cout << ' '; return 0; }
3.对象的基础 —— 结构
C语言和C++有许多共同的优美之处。其中之一便是程序员不必受限于这两种语言自带的数据类型的束缚。 C和C++的程序员完全可以根据具体情况定义一些新的数据类型并创建新类型的变量。
事实上,这个概念一直贯穿于C++的核心:对象
但首先,我们讲一个比较简单的例子:结构
结构(Structure)是一种由程序员定义的、由其他变量类型组合而成的数据类型。定义一个结构的基本语法是:
struct name { type varName1; type varName2; 。。。。。。 }; // 请注意,别忘记这个分号
当需要处理一些具有多种属性的数据时,结构往往是很好的选择。 例如当我们在编写一个鱼油档案管理程序时,涉及到的基本特征有:姓名、身份证、性别。。。
struct FishOil { std::string name; std::string uid; char sex; // F==Female, M==Male }
注意:C++对于一个结构所能包含的变量的个数是没有限制的,那些变量通常我们称为该结构的成员,他们可以是任意一种合法的数据类型。 回到刚才的例题,在定义了一个结构之后,就可以使用如下所示的语法来创建该类型的变量了:
FishOil Jiayu; // 创建一个FileOil结构类型Jiayu Jiayu.name = “小甲鱼”; Jiayu.uid = “fishc_00000”; Jiayu.sex = ‘M’;
回顾一下刚才的做法:
(1)定义结构
(2)用”.”对结构成员进行赋值
如果我们在创建一个结构类型变量的时候就已经知道它各个成员相关的值,我们可以在声明新变量的同时进行赋值。 FishOil Jiayu = { “小甲鱼”, “fishc_00000”, ‘M’ }
在C、C++里,指针无所不能,也可以指向结构,就像指向其他任何变量那样。 但我们有一个问题是:怎样才能通过指针解引用该指向结构的各个成员?(或者说是通过指针访问各个成员的值)
创建一个指向上述结构的指针: FishOil *pJiayu = &Jiayu; 注意:因为指针的类型必须与指向的地址的变量的类型一致,所以pJiayu指针的类型也是FishOil
- 我们可以通过对指针进行解引用来访问相应的变量值
(*pJiayu).name = “黑夜”; (*pJiayu).id = “fishc_00001”;
-
第二种方法
pJiayu -> name = “黑夜”; pJiayu -> id = “fishc_00001”; pJiayu -> sex = F; std::cout << pJiayu -> name; std::cout << pJiayu -> id; std::cout << pJiayu -> sex;
练习:
- 定义一个结构,至少存储:姓名、身份证、性别
- 实现文件存储
- 可以打印到屏幕
#include <iostream> #include <fstream> #include <windows.h> // 为了使用Sleep()函数 struct FishOil { std::string name; std::string uid; char sex; }; bool InitFishC(); bool ReadFishC(); void RecordFishC(); bool WriteFishC(FishOil *OilData); int main() { int i; InitFishC(); // 初始化数据。 while( 1 ) { std::cout << "请选择需要进行的操作: "; std::cout << "1. 打印数据到屏幕 "; std::cout << "2. 录入数据 "; std::cout << "3. 退出程序 "; std::cin >> i; switch( i ) { case 1: if( ReadFishC() ) std::cout << "成功读取文件^_^ "; else std::cout << "读取文件失败T_T "; break; case 2: RecordFishC(); break; case 3: return 0; } } std::cout << "初始化失败T_T...... "; return 0; } bool InitFishC() { FishOil OilInit = {"小甲鱼", "fishc00001", 'M'}; if( WriteFishC(&OilInit) == false ) std::cout << "初始化失败T_T "; } bool ReadFishC() { std::string temp; std::ifstream fileInput("FishC.txt"); if( fileInput.is_open() ) { std::cout << " 正在输出记录数据...... "; for( int i=0; i <= 100; i++ ) // 打印百分比 { std::cout.width(3); std::cout << i << "%"; Sleep(50); std::cout << ""; } std::cout << " "; std::cout << " 姓名 " << " 身份证 " << " 性别 " << " "; while( std::getline( fileInput, temp ) ) { std::cout << temp << " "; std::cout << " "; } std::cout << " "; return true; } else return false; } void RecordFishC() { char goon, Save; FishOil OilData; FishOil *pOilData; goon = 'Y'; while( 'Y' == goon ) { std::cout << "请输入鱼C账号: "; std::cin >> OilData.name; std::cout << "请输入鱼C身份证:"; std::cin >> OilData.uid; std::cout << "请输入性别:"; std::cin >> OilData.sex; std::cout << "录入成功, 请问需要保存吗?(Y/N)"; std::cin >> Save; if( 'Y' == Save ) { pOilData = &OilData; if( WriteFishC( pOilData ) ) std::cout << "成功写入文件^_^ "; else std::cout << "写入文件失败T_T "; } else { return; } std::cout << "/n请问需要再次录入吗?(Y/N)"; std::cin >> goon; } } bool WriteFishC( FishOil *pOilData ) { std::ofstream fileOutput("FishC.txt", std::ios::app); // std::ios::app用来说明在老数据追加新数据 if( fileOutput.is_open() ) { fileOutput << pOilData->name << " "; fileOutput << pOilData->uid << " "; fileOutput << pOilData->sex << " "; fileOutput.close(); std::cout << "数据成功保存到FishC.txt "; } else std::cout << "保存失败T_T "; }