第六章 复合数据类型 指针的声明与使用 数组的声明与使用 指针与数组的相互引用 字符串及相关库函数 new与delete 第六章 复合数据类型 指针的声明与使用 数组的声明与使用 指针与数组的相互引用 字符串及相关库函数 new与delete 按引用调用的参数传递方式 将函数作为参数 将数组作为参数
§6.1 变量的地址 变量在内存中占用的存储空间的首地址称为变量的地址 例:int x=0x1234; 则 x 的地址为2000H §6.1 变量的地址 变量在内存中占用的存储空间的首地址称为变量的地址 例:int x=0x1234; 则 x 的地址为2000H 0x34 0x12 2000H x
变量x以两种方式被使用 左值:变量的地址部分 右值:变量的值部分 如果一表达式的求值结果被指派了一个变量,则该表达式可做左值,否则只能做右值 例:x = x + 1 左值 右值 如果一表达式的求值结果被指派了一个变量,则该表达式可做左值,否则只能做右值 例:(1) (i<j)?i:j 做左值: (i<j)?i:j = 10 ; 做右值:k = (i<j)?i:j ; (2) i + j 只能做右值:k = i + j ; (i + j) = 1;
根据变量的使用属性,变量分: 值变量:在其存储空间中存放的是该变量的值,按变量名存取变量值; 地址变量:在其存储空间中存放的是地址,使用变量名时操作的是变量的地址; 指针类型的变量是最常用的地址变量
§6.2 指针类型与引用类型 一、指针的声明 一般形式: 一个指针占用的存储空间的大小取决于机器的内存寻址长度 基类型 *指针变量名; 基类型:是某种数据类型,指定指针变量所指向的对象的数据类型 *:指针定义符 例:int *p; int* p;int * p; 一个指针占用的存储空间的大小取决于机器的内存寻址长度 例: int age = 30; int* age_ptr; p 30 age …… 30606 age_ptr 地址30606 32820
二、指针的使用 两个相关的运算符 & 取地址运算符:取操作数的存储空间地址 * 指针运算符:用于指针变量,表示取出指针所指向的存储空间中保存的值 例: int age = 30; int* age_ptr; age_ptr = & age ; *age_ptr = 50 ; 30 age …… age_ptr 地址30606 32820 50 30606
使一指针指向某一变量时,指针的基类型必需与此变量的数据类型一致 age_ptr &age *age_ptr age *age_ptr = 50 age = 50; age++; (*age_ptr)++; *age_ptr++; *(age_ptr++); age: 30->31 *age_ptr; 地址加1 age_ptr=age_ptr+1; 使一指针指向某一变量时,指针的基类型必需与此变量的数据类型一致 int age = 30; int* age_ptr = &age; float salary = 725.50; float* salary_ptr = &salary; 那么以下赋值运算都是非法的: age_ptr = &salary; // 出错,不能将(float*)类型赋值给(int*)类型 age_ptr = salary_ptr;// 出错,不能将(float*)类型赋值给(int*)类型 salary_ptr = &age; // 出错,不能将(int*)类型赋值给(float*)类型 salary_ptr = age_ptr;// 出错,不能将(int*)类型赋值给(float*)类型
说明: 空指针NULL 指针使用前需初始化,使其指向一个明确的对象 其ASCII码为0 age_ptr = NULL ; 例: int *p ; *p = 1; 错误,p没有指向一个明确的对象,则p中存放的是一个不确定的地址,把1写到一个不确定的地方去了
三、指针运算 指针定义中的基类型决定了在指针运算中每次操作的字节数 指针只能做+、-这两种算术运算,而且只能加减整数:假设有一指针p,n是一个整数 p±n后得到的地址:p原来指向的地址± n*sizeof(p的基类型) 例: int a = 10; // 设a的地址是2000H int *p1 = &a ; /* p1存放的地址是2000H*/ p1++; /*地址变为2002H*/ char ch = ‘A’; // 设ch的地址是2010H char *p2 = &ch ; /* p2存放的地址是2010H*/ p2++; /*地址变为2011H*/ float f = 10.5; // 设f的地址是2020H float *p3 = &f ; /* p3存放的地址是2020H*/ p3++; /*地址变为2024H*/
四、引用 引用:给对象取一个别名,主要用于三个用途 独立引用 作为参数传递 作为函数返回类型
1、独立引用 一般形式: 基类型 &引用名 = 标识符; 为“标识符”所代表的变量声明一个叫“引用名”的别名 说明 基类型 &引用名 = 标识符; 为“标识符”所代表的变量声明一个叫“引用名”的别名 例: int i = 5; int &j = i ; int k = 10 ; j = k ; 说明 引用变量不另外分配内存空间 独立引用在声明时必须初始化,引用的基类型必须与建立联系的变量类型一致。引用名一旦被声明成一个变量的别名,则不能与其他变量相联系了 一个引用只能指向一个对象,一个对象则可有多个别名 i 5 j 10 i = k;
在C++的实现中对独立引用做如下限制: 不允许创建引用数组:数组不能用引用生成一个别名,但数组元素可引用 空类型void不能引用
2、引用做函数形参 按引用调用的参数传递:实现双向的参数传递,形参为实参的别名 例子:用一函数交换两个变量的值 (1) 按值参数传递 (1) 按值参数传递 void swap(int x, int y) { int temp ; 程序执行: temp = x ; ① main( ) 实参i j x = y ; ②调用swap( ) 形参x y y = temp; ③ 执行swap } ④ swap结束,x、y释放,i、j保 void main() 持原值不变 失败 { int i, j; cin>>i>>j; swap( i, j); cout<<i<<j; } 10 20 10 20 10 20
(2) 用引用做形参-按引用参数传递 void swap(int &x, int &y) { int temp ; 程序执行: temp = x ; ① main( ) 实参i j x = y ; ②调用swap( ) 形参x y y = temp; ③ 形参x、y分别是实参i、j的别 } 名,执行swap void main() ④ swap结束,x、y释放,i、j的 { int i, j; 值交换了,成功 cin>>i>>j; swap( i, j); cout<<i<<j; } 20 10 10 20
(3) 用指针做形参-按值传递,传递的是指针中存放的地址 void swap(int *p1, int *p2) { int temp ; 程序执行: temp = *p1 ; ① main( ) i j *p1 = *p2 ; *p2 = temp; 实参pi pj } ②调用swap( )形参p1 p2 void main() { int i, j; ③ 形参p1、 p2分别指向i、j, cin>>i>>j; 执行swap,交换的是p1、 p2所指 int *pi, *pj; 向的内容 pi = &i; pj = &j; ④ swap结束,i、j的值交换了, swap( pi, pj); 成功 cout<<i<<j; } 20 10 10 20 &i &j &i &j
3、引用做函数返回值 int &function( int x); void swap(int *p1, int *p2) p = p1 ; i j p1 = p2 ; p2 = p; 实参pi pj } 形参p1 p2 则交换的是形参p1、p2中存放的地址值,无法交换i、j的值,失败 3、引用做函数返回值 int &function( int x); 10 20 &i &j &i &j &i &j
§6.3 数组类型 数组:是一组具有相同名字,不同下标的数据的有序集合 数组中每一个数据称为数组元素 一、一维数组的声明 一般形式: 数组元素类型 数组名[常量表达式]; 常量表达式表示数组长度,即数组元素的个数,只能是整数常量,不能包含任何变量 例: int n; cin>>n; int a[n]; 错误,不能这样用
数组经定义后,系统为它分配一段连续的存储空间,在此空间内,数组元素依次连续存放。 存储空间大小=常量表达式的值*sizeof(数组元素类型) 例:int a[5]; 共分配 5*sizeof(int) = 10个字节的存储空间 2000H 数组名表示该数组的第一个元素 的首地址 a &a[0] 2000H 数组名是地址常量,而非地址变量 不可修改 数组的连续定义 int a[10], b[50]; a[0] a[1] a[2] a[3] a[4]
二、一维数组的使用与初始化 1、使用 数组不能以整体形式参加数据处理,只能逐个使用数组元素:数组名[下标] C++中,数组下标由0开始 例:int a[10];表示数组a有10个元素,分别是a[0]……a[9]
2、初始化 给所有元素赋初值 int a[10] = { 10,11,12,13,14,15,16,17,18,19}; // 则a[0]的值为10,a[9]的值为19 此时,可以在声明时省略数组长度 int a[] = { 10,11,12,13,14,15,16,17,18,19}; 只给前面部分元素赋初值 int a[10] = { 10,11,12}; // 则a[0]的值为10,a[1]的值为11, a[2]的值为12, a[3]至a[9]没有显式赋初值
三、数组作为函数的参数 int fun(int a[]); int fun(int *a); void main( ) { int b[10]; fun(b); } 数组做函数形式参数时,传递的是数组的首地址,因此在形参声明时,不必指定数组长度
例1: …… void bubble(int data[], int length) { int segment; // 循环扫描数组的下标变量 int loop; // 循环扫描部分数组的下标变量 int temp; // 用于交换数据时作缓冲的临时变量 // 将数组分段,逐段将数据冒泡排序 for (segment = 0; segment <= length - 2; segment = segment + 1) { // 将当前段(segment到length - 1)中的最小数据浮到最上 for (loop = length - 2; loop >= segment; loop = loop - 1) { if (data[loop + 1] < data[loop]) { temp = data[loop]; data[loop] = data[loop + 1]; data[loop + 1] = temp; } } 冒泡法:segment=0~4 segment=1~4 第一次:9 9 9 9 3 第二次:3 3 3 3 5 5 5 3 9 9 9 9 4 7 7 3 5 5 5 5 4 9 3 3 7 7 7 7 4 5 5 4 4 4 4 3 4 7 7 7 例1:
int main() { const int max_nums = 8; // 程序要处理数据的个数作为符号常量 int data[max_nums]; // 存放数据的数组 int loop; // 循环扫描数组的下标变量 // 由用户输入一些数据 cout << "Input " << max_nums << " numbers to sort:\n"; for (loop = 0; loop <= max_nums - 1; loop = loop + 1) { cout << "No." << loop + 1 << ": "; cin >> data[loop]; } // 对数据排序 bubble(data, max_nums); // 将排序后的数据输出 cout << "After sorting:\n"; cout << "No." << loop + 1 << ": " << data[loop] << "\n"; return 0;
四、二维数组的声明 一般形式 引用 数组名[行下标][列下标] 初始化 类型 数组名[常量表达式1][常量表达式2]; 例: float a[3][4]; 定义a为3行4列的数组 引用 数组名[行下标][列下标] 初始化 int a[3][4] ={{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; int a[3][4] ={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; int a[3][4] ={{1, 2}, {2, 3, 5}}; int a[][4] ={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
五、指针与数组 指针类型变量可以当作数组使用,数组类型变量也可当作指针使用。 例1: int array[5]; for (int k=0; k<5; k++) cin>>array[k]; // 当数组用 int *ptr ; ptr = array; // ptr = &array[0]; for (k=0; k<5; k++) cout<< *(ptr+k) << “ ” ; 若指针ptr已指向数组中的一个元素,则ptr+1指向同一数组中的下一个元素 cin>>array+k; // 当指针用 p[k] array[k] *(array+k)
数组名是地址常量,一经定义,其所指向的地址不可更改 指针与数组的区别 指针是地址变量,所存放的地址可随意修改 数组名是地址常量,一经定义,其所指向的地址不可更改 for (k=0; k<5; k++) cout<< *(ptr++) << “ ” ; (1) *ptr (2)ptr = ptr+1 第一次循环 取a[0]的值,然后ptr指向a[1] 但不能修改为: cout<< *(array++) << “ ” ; (1) *array (2)array=array+1 (*array)++ 则正确,array[0]++;
六、指针数组与数组指针 指针数组:基类型为指针的数组 int *a[5]; a 数组指针:基类型为数组的指针 int (*b)[5]; int array[5]; b = array; ……
字符串:由字符组成,以‘\0’(字符串结束符,其ASCII码值为0) 结束。 例:“China” 存储组织为: §6.4 字符串 一、字符串 字符串:由字符组成,以‘\0’(字符串结束符,其ASCII码值为0) 结束。 例:“China” 存储组织为: 字符串长度:所有字符的个数(不包括结束符)。 字符串的初始化 例:char str[] = { “I am happy” } ; //字符串长度10,数组长度11 char str[] = “I am happy” ; char str1[] = {‘I’, ‘ ’, ‘a’, ‘m’, ‘ ’, ‘h’, ‘a’, ‘p’, ‘p’, ‘y’}; //数组长度10 ‘C’ ‘h’ ‘i’ ‘n’ ‘a’ ‘\0’
二、字符数组和字符指针 字符数组 字符指针 定义 char str[数组长度]; char *s; 内存分配 占用一段连续的 存储空间 占用一个指针长 度的存储空间 变量名的使用 str是地址常量,不能用于赋值号的左边 指针名代表“地址”,只有让它指向一个明确的对象时,才有指向,是地址变量 赋初值 (1) char str[]=“china”; (2) char str[80]; str = “china”; (1) char *s =“china”; (2) char *s ; s = “china” ; 与(1)等价 赋值 (1) 对字符数组的各个元素赋值: str[k] = ‘a’ ; (2)使用库函数: strcpy(str, “china”); (1)直接用赋值号对整个字符串赋值: s = “china” ; (2)当使用库函数时,指针s必须已指向一个明确对象: s=str; strcpy(s, “china”); str s
例:字符串的复制 例1: #include <iostream.h> void copy_string(char from[], char to[]) { int k = 0; while (from[k] != ‘\0’) { to[k] = from[k]; k++; } to[k] = ‘\0’; main() { char a[]=“I am a teacher”; char b[20]; copy_string(a,b); cout<<b; return 1;
例2: void copy_string(char *from, char *to) { int k = 0; while ((to[k] = from[k])!= ‘\0’) k++; } 例3: for (; *from != ‘\0’; from++, to++) *to = *from; *to = ‘\0’; 例4: while ((*to = *from)!= ‘\0’) { from++; to++; 例5: while (( *to++ = *from++) != ‘\0’); *(to++)= *(from++) (1) *to = *from (2)判断*to != ‘\0’ (3)to=to+1;from=from+1 例6: while ( *to++ = *from++) ;
§6.5 指向对象的指针 一、对象指针 若声明的一个指针的基类型是一个类类型,那么这个指针称为对象指针,它指向一个类对象。如 CIRCULAR_NUMBERS* angle_ptr; CIRCULAR_NUMBERS angle(0, 359, 250); angle_ptr = & angle ; 通过对象指针来访问对象的成员时,必须使用成员指针运算符 -> angle_ptr->increment(); angle.increment();
this 指针 C++为每个对象提供一个指针this,记录该对象的地址,当通过对象来调用类的成员函数时,this作为一个隐式参数传递给成员函数 this的性质 隐式指针,不能显式声明 常指针,不能被修改,当一个类对象被创建时,this就被初始化为指向该对象的指针 this是一个局部变量,局部于某个对象,在该类的任何非静态成员函数中可见。
例1: CIRCULAR_NUMBERS angle(0,359,250); // 角度循环计数器 CIRCULAR_NUMBERS month(1,12,11); angle.set_value(100); set_mode()代码 increment()代码 decrement()代码 set_value()代码 get_value()代码 对象angle min_val 359 max_val 250 current set_mode() set_value() get_value() increment() decrement() 类 CIRCULAR_NUMBER 对象month 1 12 11 this this 100 this 例2: int C::get_value( ) { return value; return this->value; }
例3:在一个双向链表中插入一个链表项(把一个链表项说明为一个类) // 该类的对象是链表中的一个节点 class dLink{ private: …… dLink *pre; //指向链表中的前一个项 dLink *suc; //指向链表中的后一个项 public: void append(dLink *p); …… }; NULL …… pre suc …… pre suc NULL …… pre suc
void dLink::append(dLink *p) p->suc = suc ; // p->suc = this->suc ; (1) p->pre = this; (2) if (suc!=NULL) suc->pre = p ; (3) suc = p; (4) } void main() { dLink A; dLink B,C; A.append(&B); A.append(&C); A B pre suc NULL …… NULL …… pre suc (3) (4) (2) (1) C pre suc ……
二、C++语言中的动态创建与撤销 运算符new提供了动态存储分配的功能 一般形式: 指针名 = new 基类型名(初始化表); 若基类型是类类型,则()内为该类的构造函数提供实参 功能:动态分配一块内存,其大小由基类型决定,若分配成功,则返回该内存的首地址给指针,否则返回NULL
例1: int *p ; p = new int; *p = 10; cout << *p; 例2:动态分配数组 int *p1; int len; cin>>len; p1 = new int[len]; 例3:分配类对象 CIRCULAR_NUMBERS* angle_ptr; angle_ptr = new CIRCULAR_NUMBERS(0, 359, 250); p 10 p 10
运算符delete用于释放用new分配的存储空间 一般形式: delete 指针名; 例: delete p; delete []p1; //数组释放时,加[]则会释放整个分配的区域,否则只会释放一个基类型占用的空间 delete angel_ptr; 用new分配的内存空间,一定用delete手动释放,而且只能释放一次
§6.6 指向函数的指针 一、函数指针 函数的名字代表函数的入口地址 用于存放一个函数的入口地址,指向一个函数。通过函数指针,可以调用函数,这与通过函数名直接调用函数是相同的
二、使用函数指针调用函数 函数指针的定义 数据类型 (*指针变量名)(函数形参表); 数据类型:此指针所指向的函数的返回值类型 例: int (*p1)(int); p1是指向(有一个int形参、返回整型数据的)函数的指针--函数指针p1的定义 int *p2(int); p2是一个函数,有一个int形参,返回值为指向整型的指针--函数p2的原型 函数指针一经定义后,可指向函数类型相同(即:函数形参的个数、形参类型、次序以及返回值类型完全相同)的不同函数 例: int max(int, int); int min(int,int); int (*p)(int, int);
给函数指针赋值,使指针指向某个特定的函数 函数指针名 = 函数名; 例: p = max ; 将函数max的入口地址赋给p指针, 则p指向max函数。 用函数指针变量调用函数 (*函数指针)(实参表); 例: int a,b,c; cin>>a>>b; c = (*p)(a,b); 调用p指向的函数,实参为a、b, 得到的函数值赋给c c = max(a,b);
三、函数指针作为参数 函数指针也可以做参数,以便实现函数地址的传递 Sub( int (*p1)(int), int (*p2)(int,int) ) { int a,b,i,j; …… a = (*p1)(i); b = (*p2)(i,j); } main() { int f1(int); int f2(int, int); Sub(f1, f2);
§6.7 结构类型、枚举类型与类型别名 一、结构类型 §6.7 结构类型、枚举类型与类型别名 一、结构类型 结构体的声明:得到一种新的数据类型 struct 结构类型名{ 成员列表; }; 例:定义一个复数结构 struct complex{ float re ; float im; 结构体与类的区别 C++中,结构体与类一样不但能有数据成员,也可定义成员函数 缺省时,类的成员是私有的,而结构类型的成员则是公有的
complex x = {10.2, 20.5}; 定义结构类型变量 结构体变量的引用 complex x; 结构体变量的初始化 complex x,y; 结构体变量的引用 直接使结构类型变量 complex x; x.re = 10.2 ; x.im = 20.5 ; 通过指向结构类型对象的指针来访问 结构体变量的初始化 complex x = {10.2, 20.5}; struct STUDENT { long int serial_number; // 学号 char name[10]; // 姓名 int sex; // 性别 int age; // 年龄 char department[30]; // 所属系 }; STUDENT a={20010346, ”Zhang” , 1, 18, ”Computer” };
二、枚举类型 如果一个变量只有几种可能的值,则可声明一个枚举类型 enum 枚举类型名{枚举元素1, 枚举元素2, ……}; 例:enum WEEKDAY{sun, mon, tue, wed, thu, fri, sat}; WEEKDAY day ; day = mon ; 枚举元素是(不重复的)整型常量,缺省时,按顺序为每个元素指定常量值。sun为0,mon为1 可显式为枚举元素指定常量值 enum META_COLOR {// 基本颜色类型(RGB) RED = 4, // 红色 GREEN = 2, // 绿色 BLUE = 1 // 蓝色 }; 若类型声明时为某个元素指定常量值,那么其后没有显式赋值的元素,其值为上一个常量值加1
枚举值可做判断 if (day>tue) …… 不能把整数直接赋给枚举变量 day = 2 ; 错 enum COLOR { // 颜色类型 RED, // 红色RED的值为0 GREEN = 5, // 绿色GREEN的值为5 BLUE = 6, // 蓝色BLUE的值为6 CYAN // 青色CYAN的值为7 }; 枚举值可做判断 if (day>tue) …… 不能把整数直接赋给枚举变量 day = 2 ; 错 day = (WEEKDAY) 2 ; 对
三、typedef 类型定义 typedef 旧类型 新的数据类型名; 一般形式: typedef int INTEGER; INTEGER k; int k; typedef struct complex{ float re ; float im; } COMPLEX ; COMPLEX x; typedef int NUM[100]; NUM n; int n[100];
typedef char *STRING; STRING p, s[10]; char *p; char *s[10]; typedef int (*POINTER)(); POINTER p; int (*p)(); typedef void FUNC(char *,int &); typedef FUNC *pFUNC; pFUNC pp; void (*pp)(char *,int &);