320 likes | 479 Views
第 4 章 C++ 语言的复合数据类型. 本章要点: 指针的定义和使用 引用的定义和使用 数组的定义和使用 vector 容器的定义和使用 字符串的定义和使用 枚举的定义和使用. 4.1 循序渐进学理论. 4.1.1 指针的定义和使用. 1. 指针的概念 指针变量是一种特殊的变量,用于保存某个变量、对象或者函数的地址值。可以通过指针对那些存放在存储器中的数据进行读写操作。. 2. 指针的定义 指针的定义格式如下(方括号“ [ ]” 中的内容是可选的):
E N D
第4章 C++语言的复合数据类型 本章要点: 指针的定义和使用 引用的定义和使用 数组的定义和使用 vector容器的定义和使用 字符串的定义和使用 枚举的定义和使用
4.1 循序渐进学理论 4.1.1 指针的定义和使用 1. 指针的概念 指针变量是一种特殊的变量,用于保存某个变量、对象或者函数的地址值。可以通过指针对那些存放在存储器中的数据进行读写操作。
2. 指针的定义 指针的定义格式如下(方括号“[ ]”中的内容是可选的): [存储类型] <类型> *<指针名> [= <初值>] 例如: int *ip1, *ip2; // 定义指向int型对象的指针ip1和ip2 每个指针都必须要指定相关的数据类型,这对指针来说是非常重要的,因为在指针变量中存放的内容是地址,而不是具体的数据,编译器只能根据指针的数据类型来决定如何操作该指针指向的数据。
3. 指向常量的指针和常量指针的定义 指向常量的指针本身并不是常量,而是指向一个常量对象,通常用作函数的形参,确保传递给函数的对象在函数体内不会被改变。 例如: const double *dp; // dp是指向double型常量的指针 也可以定义一个常量指针,其自身是常量,不能被改变,但是可以通过它指向其他的常量或者变量。常量指针的定义看上去有些别扭,例如: const double *const dp1; double *const dp2;
4. 指针变量的赋值 指针在没有被赋值前是不能被使用的,给指针变量赋值有三种形式:取地址符“&”赋值、指针间赋值和数组名或者函数名赋值。C++语言规定“数组名”和“函数名”代表数组或函数的地址。下面是一些为指针赋值的示例: int ival = 2938; int *ip1 = 0; int *ip2 = &ival; ip1 = ip2; // ① 注意:指针不能被赋予不是地址的值,也不能被赋予与该指针类型不同的其他类型的对象的地址,这些都会导致编译错误。有一个特例,空指针能够被赋予任何类型的对象的地址。
5. 指针的使用 指针能够被用来作为函数参数,传递数组或类对象,或者管理动态分配的变量和对象,最典型的用法是构造链表和树。 通过解引用运算符“*”能够间接访问指针所指向的对象,例如: int a, *pa, b = 2032; pa = &a; // 通过赋初值使指针变量pa指向a *pa = b; // 将b的值赋给pa指向的变量,即a = 2032; 指针变量的本身也可以用于算术运算,常见的运算有: (1)指针变量+整数 (2)指针变量–整数 (3)指针变量++ ++指针变量 指针变量-- --指针变量 (4)指针变量1–指针变量2
4.1.2 引用的定义和使用 1. 引用的概念 所谓引用,就是某一个变量或者对象的另一个名字,所以又被称作“别名”。比如,唐朝大诗人李白,字太白,“太白”二字就是李白本人的一个引用。 引用不是变量,其本身没有值和地址,不能占用任何的内存空间。其典型用法是用作函数的形参和返回值。
2. 引用的定义 引用的定义格式如下: <类型> &<引用名> = <初始值>; 其中,引用的类型必须和被引用变量或对象的类型相同,“&”是取地址符。引用在被定义的同时,必须被初始化,指出该引用是哪个变量或对象的别名,否则会导致编译错误。一旦被初始化,该引用就不能再指向其他的变量或对象。例如: int ha = 256, *pi = &ha; int &refHa1 = ha; // 指向整型变量ha的引用 int *&refHa2 = pi; // 指向整型指针pi的引用。
3. 引用的使用 引用只是某个变量或对象的“别名”,它的值和地址都与被引用的变量或对象的值和地址值相同,对引用的所有操作实际上都是应用在被引用的变量或对象身上。 指针和引用的区别: (1)引用在定义时必须被初始化,而指针则可以随意; (2)指针可以被引用,但引用不能被引用; (3)指针是变量,可以指向不同的变量或对象,而引用 必须始终指向某个变量或对象。
【例4-2】分析下列程序的运行结果。 #include "stdafx.h" #include <iostream> using namespace std; void main() { int ival = 2048, a = 256, *pi = &a; int &refVal = ival; int *&refPi = pi; ival ++; // ① cout << "refVal = " << refVal <<endl; refVal = a; // ② cout << "ival = " << ival<<endl; *refPi = 1024; // ③ cout << "a = " <<a<<endl; }
4.1.3 数组的定义和使用 1. 数组的概念 数组是相同类型的数据的集合,其大小必须为某个固定值。它为处理一组相同性质的数据提供了方便,而实际上,很多问题如果离开了数组基本上是无法实现的。 例如: float cj[10] = {90, 83, 95, 93, 92.5, 100, 96.5, 84, 90, 76.5} 数组中的每一个成员被称为数组元素,可以通过可变的下标或指针来访问它们。
2. 数组的定义 (1)一维数组的定义 <存储类型> <数据类型> <数组名> [长度]; 存储类型可以是自动型(auto)、静态型(static)、外部参照型(extern),但不能是寄存器型(register),缺省值为自动型。数据类型可以是基本类型、复合类型,但不能是引用。 (2)多维数组的定义 <存储类型> <数据类型> <数组名> [第1维长度] [第2维长度]; 例如:int a[3][4];
3. 数组的赋值 • (1)一维数组赋初值 • 可以用一组初值对一维数组进行初始化,这组初值放在花括号“{}”内,初值与初值之间用逗号“,”隔开。例如: • int a[5]={1, 2, 3, 4, 5}; // 为全部的元素赋初值 • 如果初值的数目N少于维数M,则用这些初值去初始化数组中的前N个元素,剩余的(M-N)个元素会被自动赋予相应的初值。如果初值的数目N多于维数M,则会导致编译错误。
(2)二维数组赋初值 • 可以分行给二维数组中的元素赋初值,例如: int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}; • 如果赋值的数值的数目少于数组的维数,即只给部分元素赋初值,没有被显式赋初值的元素也会有初值,对于数值型数组,其值为0,对于字符型数组,其值为‘\0’。 • 给多维数组赋值时,花括号中包含的子括号可以被省略,例如: int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11}; 需要注意的是,并不是任何时候都可以省略子括号的,当部分赋值时,子括号的省略会引起混乱。
4. 数组的使用 对数组元素的操作有两种方式:一是通过下标运算符“[ ]”来读写数组的元素,二是通过移动的指针来读写数组的元素。 (1)下标运算符 采用下标运算符的数组元素的格式为: <数组名>[<下标>] 注意:数组元素的下标从0开始。系统不会自动检查下标是否越界,在编写程序时,必须仔细检查数组的下标,防止下标越界。
pa a[0] a[0] a[1] pa+2 a[2] a[3] a[4] (2)一维数组和指针 C++语言规定,一维数组的数组名代表着该数组的首地址,即第一个元素的地址。我们可以定义一个与数组元素类型相同的指针,再将数组的首地址赋给它,然后通过该指针读写数组中的元素。例如: int a[5] = {0,1,1,2,3}; int *pa = a; // 定义一个指向数组a的指针 pa、a与数组的关系可用图4-10表示。 图4-10 指向一维数组的指针变量示例
(3)二维数组和指针 二维数组的指针表示比较复杂,和一维数组不同的是,数组名不再表示首地址,而是一个指向存放首地址的地址。 假设有二维数组b: b[M][N] 该二维数组的首地址为*b,或b[0],第1个元素的值为元素的地址**b,或为*b[0]。 第i行第0列的元素的地址用指针表示为*(b+i),该行中第j列元素的地址用指针表示为*(b+i)+j,那么,二维数组元素b[i][j]的值用指针表示为*(*(b+i)+j)。
1. vector容器的概念 在C++语言中,有两种面向对象的抽象容器类型──顺序容器和关联容器。其中,顺序容器有list和vector容器,关联容器有map和set容器。 和数组相同的是,vector容器占用一段连续的内存存储空间,顺序存放每一个vector元素,不同的是,vector容器没有固定的长度,当长度超过容器的容量时,系统为它分配双倍于当前容量的存储空间,把当前的元素拷入新的存储区。 为了使用vector,在程序中必须包含头文件vector: #include <vector> 4.1.4 vector容器的定义和使用
2. vector容器的定义和初始化 vector容器的定义格式如下: vector <数据类型> <容器名>; 例如:vector <int> month_days; • 可以定义一个已知长度的vector容器,并为它赋以初值,格式如下: • vector <数据类型> <容器名> (<长度>, <初值>); • 例如:vector <int> month_days(12, 30); // 12个int类型元素,每个元素的初始值为30。 • 还可以用一个已经定义了的数组来初始化vector容器,格式如下: • vector <数据类型> <容器名> (<开始地址>, <结束地址>); • 例如: • int a[10] = {0,1,2,3,4,5,6,7,8,9}; • vector <int> avec(a+2, a+5);
3. vector容器的使用 C++语言支持vector容器间的赋值和比较、元素的插入和删除、容器容量和长度的查询等操作。 • 在容器中插入元素的方法是: insert (<插入的地址>, <插入的数量>, <元素值>) 如果插入的数量为1,则可以被省略。例如,在容器的首部插入1个元素a: ivec.inset(ivec.begin(), a); • 在容器中删除元素的方法有: erase (<删除元素的地址>); // 删除一个元素 erase (<删除元素的起始地址>, <终止地址>);
可以用下标运算符来访问vector容器的元素。但是,更常用的是使用类似指针的迭代器。可以用下标运算符来访问vector容器的元素。但是,更常用的是使用类似指针的迭代器。 • 首先,要定义一个迭代器,例如: • vector <int>::iterator iter = ivec.begin(); • 迭代器的使用方法和指针相似,可以施加算术运算(只适合元素在内存中连续存储的容器),也可以解引用后访问它所指向的元素。 • 例如,显示容器ivec中的每一个元素: • for (; iter != iter_end; ++iter) • cout << *iter;
1. 字符串的概念 程序中的数据不仅有整数、浮点数,还有姓名、住址、输入输出的提示信息等一串串有意义的字符序列。例如: "Enter your name: " "The area of circle is“ 这些字符的组合被称为字符串,由若干个字符和一个终止结束符'\0'组成。C++语言提供了两种字符串的表示方式:字符数组和string类,前者继承于C语言,有着许多面向过程的痕迹,建议读者采用string类字符串。 4.1.5 字符串的定义和使用
2. 字符串的定义 (1)字符数组中的字符串 在该方式中,字符串存放于一个字符数组,通常用一个指向该数组的指针来表示,定义格式如下: const char *<字符串名> = "<字符串值>"; 例如: const char *address = "Guangzhou China"; 该语句定义了一个字符型指针address,指向一个常量字符数组。需要注意的是,该数组的最后一个元素应该是字符串结束标记'\0',而不是字符串值"Guangzhou China"的最后一个字母'a'。
(2)基于string类的字符串 C++语言提供了字符串类string,支持字符串的初始化、字符串间的赋值、拷贝、比较和连接,支持单个字符的读写、字符串长度的查询等操作。 在使用类string之前,必须在程序中包含头文件string: #include <string> string字符串的定义格式如下: string <字符串名>; string <字符串名> (<初始值>); 例如: string name; // 空字符串name string name ("My name is C++!");
3. 字符串的使用 (1)字符数组中的字符串 • 存储在字符数组中的字符串不能够直接支持字符串间的赋值和连接,但是可以利用一些函数查询字符串的长度、字符串间的拷贝和比较,例如: int strlen(const char *); int strcmp(const char *, const char *); char * strcpy(const char *, const char *); • 在使用这些函数之前,程序中必须包含头文件ctring: #include <ctring> • 对于字符数组中的字符串,可以利用移动的指针来访问其中的字符。
(2)基于string类的字符串 • 使用基于string类的字符串要简单和方便的多,字符串间的拷贝和连接可以通过算术运算符来实现。例如: s1 = s2; // 将s2的值赋给s1,即将s1拷贝给s2 string s3 = s1 + s2; // 将s1和s2连接起来赋给s3 • 通过成员函数size()可以读取字符串的长度,例如: if (!s3.size()) // 如果字符串长度不为空,那么…… • 如果想访问string字符串中的字符,可以通过下标运算符,也可以通过迭代器。例如,用下划线代替字符串中的空格: for (int i=0; i<a.size();++i) if (a[i]== ' ') a[i]= '_'; 或者采用迭代器: replace (a.begin(), a.end(), ' ', '_');
1. 枚举的概念 所谓枚举,就是一个整数常量的集合,它与const定义常量的不同之处在于能够将传递给函数的参数值限定在该集合中。 4.1.6 枚举的定义和使用
2. 枚举的定义 枚举类型的定义格式如下: enum <枚举类型名> (<枚举成员1>, <枚举成员2>, …… <枚举成员n>); 例如: enum Color (RED, ORANGE, YELLOW, GREEN); • 其中,各“枚举成员”的名称是标识符,且均有一个初值。RED被赋值为0,ORANGE被赋值为1……依此类推, GREEN被赋值为3。 • 也可以在定义枚举时,为各个成员指定特定的值,值可以不是唯一。例如: enum A (grandpa = 0, grandma = 0, father =1);
4.2 典型实例练能力 4.2.1 典型实例一: 根据货币总值计算最少的人民币张数 【实例题目】 简便起见,假设有下列7种面值的人民币:100元,50元,20元,10元,5元,2元和1元。任意输入一个整数m,计算并显示如何使用最少的人民币纸张,使货币总值为m。 例如,输入数据为193,则最少需要6张人民币纸张:196 = 1*100 + 1*50 + 2*20 + 1*5 + 1*1
4.2.2 典型实例二:计算学生的平均成绩 【实例题目】 编写一个程序实现以下功能:某班有M名同学,本学期开了N门课,期末考试后,分别统计每个学生的平均分。简便起见,假设M=3,N=4,成绩为整数。 要求:采用指针读写数组中的元素。 输入:学生总数M和课程数目N,对于每个学生,输入学号和各课的成绩。
4.2.3 典型实例三:智者生存 【实例题目】 17世纪法国数学家加斯帕在《数学的游戏问题》一书中提出了有趣的约瑟问题:“15名基督教徒和15名异教徒同船航行,途中风浪大作,危机万分,只有将船上一半的人投入海中,其余的人才能幸免。于是,这30个人围成一圈,由第一个人起报数,每数至第9人便把他投入大海,然后从1开始重新报数,第9人又被投入大海,依次类推,直至船上剩下15人为止。问题是如何排法才能使被投入海中的全是异教徒?” 要求:采用vector容器解决这个问题。
4.3 上机练习重应用 4.3.1 上机练习一:合并两个有序数组 【练习题目】 假设存在以下两个数组A和B,各个数组中的元素已经按照升序排列。编写一个程序,合并数组A和B,形成一个新数组C,并使数组C中的元素仍按升序排列。 int A[10]={-5,-2,0,4,16,23,55,80}; int B[10]={-13,-4,5,30,55,68,100,120};