第六章复合数据类型第六章 复合数据类型
§ 6.1 指针类型
§ 6.2 数组类型
§ 6.3 字符串
§ 6.4 指向对象的指针
§ 6.5 指向函数的指针
§ 6.1 指针类型
指针概念
指针的定义
指针运算
引用类型
§ 6.1 指针类型指针概念
指针:是一种数据类型
指针变量:具有指针类型的变量
指针变量的值:某变量的地址值
指针变量的类型:它所指向的变量的类型指针可以指向任何数据类型,可以指向数组、指向函数、指向文件、指向指针(多级指针)
§ 6.1 指针类型
如:
int age=20;
int* ptr;
ptr=&age;
内存 内存地址
20 324848
324848
age
ptr
……
……
……
365626
§ 6.1 指针类型
指针本身也是一个变量
ptr=&i;
ptr=&j;
2df4(2df8)
156
99
1234
1238
123c
2df0
2df4
2df8
……
i=156
j=99
ptr=2df4
ptr=2df8
指针变量的值可以改变
§ 6.1 指针类型
指针的定义必须“先声明,后使用”
int* ip; //定义一个指向整型数的指针 ip
float* fp; //定义一个指向浮点数的指针 fp
char* cp; //定义一个指向字符型的指针 cp
int* (ipa)[10]; //指向整型数组的指针
int** ipp; //指向指针的指针
§ 6.1 指针类型
指针定义方法在类型名后或指针变量名前加,*”
如:定义一个指针变量 ip(可用以下格式)
int *ip;
int* ip; //在一条语句中定义的多个变量都必须是指针
§ 6.1 指针类型
指针本身只有一种各种类型指针变量的内存长度一样
(32位机,占 4个字节 )
可以用 sizeof( )函数进行测试:
即,sizeof(int*)== sizeof( float*)
== sizeof( char*)== sizeof( char**)
== ……
§ 6.1 指针类型
指针的初始化 (可在指针定义时初始化)
例:
int i1,i2;
int ia[10];
int* p1=&i1; //用变量的地址初始化指针
int* p2=p1; //用另一个指针初始化指针
int* p3=ia; //用数组名初始化指针
§ 6.1 指针类型
指针运算指针变量的两个运算符:,*”和,&”
“*” ——取出指针所指的变量地址中的值
,&”——取出操作数的地址
指针是一种特殊的变量,指针运算一般包括指针 赋值运算 和指针 加减运算
§ 6.1 指针类型
指针赋值例:
int i1,ia[10];
int* p1,p2,p3;
p1=&i1; //可以用变量的地址赋值
p2=p1; //可以用另一个指针赋值
p3=ia; //可以用数组名赋值
p3=NULL; //空指针,不指向任何空间
§ 6.1 指针类型
例:
int i,j;
int* p=&i;
*p=45; //等同于 i=45
j=*p; //等同于 j=i
p=&j; //p指向 j
*p=189; //等同于 j=189
§ 6.1 指针类型
“*” 的作用(两种)
-指针定义时,,*” 表示定义的是指针
-指针运算时,,*” 表示指针所指向的内存单元中的值注,*p能否作为左值与它所指向的空间能否作为左值相关例,int i; 例,const int x=5;
int* p=&i; const int* q=&x;
*p=45; *q=89;
§ 6.1 指针类型
“&” 的作用:取地址
(只能用于可以取出地址的表达式)
例:
p=&45; //error! 常数没有固定地址
p=ar+3; //允许,ar是数组名
p=&i+&j; //i,j都是合法地址,但相加不一定合法
p=&i+4; //允许,但较危险,地址加常量不一定是地址
§ 6.1 指针类型
指针运算(加运算)
例:
int* p;
p=p+1;
注:不是地址加 1,而是加一个整型数
§ 6.1 指针类型
指针运算(减运算)
在一定条件下,两个指针可以相减例:指向同一个数组不同元素的两个指针相减,
其差是这两个指针之间相隔元素的个数如:一个字符串,一个指针指向首字符,另一个指针指向尾字符,两指针之差就是字符串长度
§ 6.1 指针类型
指针运算在一定条件下,两个指针可以相比较例:指向同一个数组的两个指针可以比较,
当两指针相等时,说明这两个指针指向同一个数组元素
§ 6.1 指针类型引用类型,在某个数据类型后加,&”
例,int i=1;
int& ir=i; // int& 是整型 int的引用类型
引用类型的变量 (ir)并不是真创建的新变量,
而是作为另一个变量 (i)的别名
引用类型的变量必须初始化
§ 6.1 指针类型
int i=1;
int& ir=i;
引用 ir作为 i的别名后,对 ir的操作就相当于作用在变量 i上如:
ir=10; //相当于 i=10;
cout<<&ir; //相当于输出 i的地址
§ 6.1 指针类型引用类型的作用
用来作函数的参数或返回值
(按引用调用的参数传递方式)
例:交换两个整数
void swap(int& x,int& y)
{
int temp;
temp=x;
x=y;
y=temp;
}
§ 6.1 指针类型
void main( )
{
int i,j;
cout<<“Input two numbers:”;
cin>>i>>j;
cout<<“Before swapping:”<<i<<?,?<<j<<endl;
swap(i,j);
cout<<“After swapping:”<<i<<?,?<<j<<endl;
}
//形参 x,y分别是实参 i,j的别名,对形参的修改即对实参的修改
§ 6.1 指针类型
按引用调用的好处
-比按值调用的效率高,不用复制实参的副本
-可返回多个值,相当于对实参操作
§ 6.2 数组类型
数组定义
数组元素的访问
数组元素的初始化
数组作为函数参数
多维数组
指针与数组
指针数组与数组指针
§ 6.2 数组类型数组定义
数组元素的类型可以是任何数据类型
char ca[10]; //字符数组 表示学生
int ia[10]; //整数数组 表示年龄
float fa[10]; //浮点数组 表示成绩
double da[10]; //双精度数组
int ia[10][10]; //数组的数组 二维数组
PEOPLE ra[10]; //对象数组
int* pa[10]; //指针数组
§ 6.2 数组类型
数组元素的个数是整型常量表达式例:
const int array_size;
char ca[0+2];
int ia[array_size];
float fa[array_size*2];
§ 6.2 数组类型
[ ]和 ( )的含义不同
int a[5]; //定义一个具有 5个元素的数组
int a(5); //定义一个初始值为 5的整型变量
§ 6.2 数组类型
数组表示内存中一块连续的区域
long int a[5];
a[0]
a[1]
a[2]
a[3]
a[4]
aa
§ 6.2 数组类型
数组元素的类型决定了每次偏移的大小
a[0]
a[1]
a[2]
a[3]
a[4]
a b[0]
b[1]
b[2]
b[3]
b[4]
b
long a[5]; short b[5];
a
b
每个元素占
4个字节 每个元素占 2个字节
§ 6.2 数组类型数组元素的访问
可以作为左值
int a[10];
for(int i=0; i<10; i++)
a[i]=i*3;
可以作为右值
int a[10];
for(int i=0; i<10; i++)
cout<<a[i];
下标不能越界!
数组元素的下标从 0
开始
§ 6.2 数组类型
数组元素的初始化
int ia[10];
int ia[10]={0,1,2,3,4,5,6,7,8,9};
int ia[10]={0,1,2,3};
int ia[]={0,1,2,3,4,5,6,7,8,9};
int ia[10]={0,1,2,3,4,5,6,7,8,9,10,11};
int ia[10]={0,1,2,3,};
int ia[10]={};
int ia[];
§ 6.2 数组类型
数组大小的确定用 sizeof( )可返回整个数组所占空间
int ia[ ]={45,32,58,99,18};
int len;
len=sizeof(ia)/sizeof(int); //可得到数组长度
§ 6.2 数组类型数组使用规则
使用数组的方式是数组名加下标
数组下标是整型常量表达式
数组下标从 0开始
数组下标不能越界
§ 6.2 数组类型
数组作为函数参数
int ia[5];
//在 main()中
func(ia); //函数调用
……
void func(int iarray[])
{
……
}
ia[0]
ia[1]
ia[2]
ia[3]
ia[4]
ia
程序代码程序数据堆栈内存
ia的地址数组是按地址传递
§ 6.2 数组类型数组作为函数参数
一般要把数组的大小传递给函数
int average(int ia[],int len)
{
int sum=0;
for(int i=0; i<len; i++)
sum+=ia[i];
return sum/len;
}
§ 6.2 数组类型
int average(int ia[])
{
int len=sizeof(ia)/sizeof(int);
int sum=0;
for(int i=0; i<len; i++)
sum+=ia[i];
return sum/len;
}
//数组作函数参数传递时会丢失数组的信息
ia是一个地址
(指针 ),占 4个字节
//len 是错误的 !
§ 6.2 数组类型
书上第 155页 冒泡程序
§ 6.2 数组类型多维数组
以数组作为数组的元素类型
char name[10][20]; //二维数组
int iarray[10][10][10]; //三维数组
§ 6.2 数组类型
多维数组的理解
int a[4][4];
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
a[3][0] a[3][1] a[3][2] a[3][3]
a[0][0]
a[0][1]
a[0][2]
a[0][3]
a[1][0]
a[1][1]
a[1][2]
a[1][3]
a[2][0]
a[2][1]
a[2][2]
a[2][3]
a[3][0]
a[3][1]
a[3][2]
a[3][3]
a[0]
a[1]
a[2]
a[3]
§ 6.2 数组类型多维数组的初始化
按行进行初始化
int b[2][3]={{1,2,3},{4,5,6}};
int b[2][3]={{1,2,3,4,5,6}};
int b[2][3]={{1,2},{4,5,6}};
int b[2][3]={{1,2,3}};
int b[ ][3]={1,2,3,4,5,6,7,8};
注:第二维的大小不能省略例,int b[][]={1,2,3,4,5,6,7,8};
1 2 -
4 5 6
1 2 3
- - -
1 2
3
4 5
6
7 8 -
§ 6.2 数组类型多维数组作为函数参数
也是按地址传递只能省略第一维的大小后面的维数不能省略
可以降维处理
int a[10][10]
func(&a[0][0],10*10)
§ 6.2 数组类型
例:
void func(int a[][10]);
void main( )
{
int a[10][10];
func(a);
}
void func(int a[][10])
{
cout<<sizeof(a)/sizeof(int); //结果为 1
cout<<sizeof(a[0])/sizeof(int); //结果为 10
}
§ 6.2 数组类型指针与数组
数组名实际上是一个指针
int ia[5];
int* p=ia;

int ia[5],*p;
p=&ia[0];
p=ia;
ia[0]
ia[1]
ia[2]
ia[3]
ia[4]
ia
p
等价
§ 6.2 数组类型
下面的式子等价
p ia
*p ia[0] *ia p[0]
*(p+i) ia[i] *(ia+i) p[i]
p+i ia+i &ia[i] &p[i]
§ 6.2 数组类型
遍历数组
for(int i=0; i<10; i++)
cout<<ia[i];
for(int i=0; i<10; i++)
cout<<*p++;
//或 p[i]或 *(p+i)或 *(ia+i)
§ 6.2 数组类型数组与指针的不同( 1)
数组名是一个指针常量,不能修改值如:
ia++;
ia[0]
ia[1]
ia[2]
ia[3]
ia[4]
ia
p
P+=2;
§ 6.2 数组类型数组与指针的不同( 2)
int ia[5];
int* p=ia;
cout<<sizeof(ia);
cout<<sizeof(p);
//输出 20,指向整个数组的空间
//输出 4,只是指针变量自己
//数组有局限性,空间是固定的,而指针是灵活的
§ 6.2 数组类型指针数组与数组指针
指针数组数组中的每个元素都是一个指针例:
int *ptr_array[10];
int age1=30,age2=54; //[ ]的优先级高于 *
ptr_array[0]=&age1; //指针数组中第一个元素指向 age1
ptr_array[1]=&age2; //指针数组中第二个元素指向 age2
int *(ptr_array[10]);
§ 6.2 数组类型指针数组与数组指针
数组指针
例:
int (*p)[3]; //p是一个指向一维数组的指针,该数组有 3个 int型元素
§ 6.3 字符串
字符数组
指向字符串的指针数组
关于字符串操作的库函数
§ 6.3 字符串字符串:,This is a program!\n”
长度:字符总数 +1
字符总数包括:空格、标点、转义字符
C++没有提供字符串类型,它用数组和指针来处理字符串
§ 6.3 字符串
字符数组字符串变量的类型:字符数组同样遵循“先声明,后使用”
§ 6.3 字符串
字符数组的定义及初始化
char name[10];
char name[10]={“Hello”};
char name[10]={?H?,?e?,?l?,?l?,?o?,?\0?};
char name[10]=“Hello”;
char name[ ]={“Hello”};
注:
1、字符串以‘ \0?结束,而字符数组不一定以‘ \0?结束
2、字符串长度不能超过下标
§ 6.3 字符串
字符数组的使用
for(i=0; i<10; i++)
cin>>name[i]; //访问每一个字符
以 ‘ \0?结束的字符串数组可以直接输入和输出例:
cin>>name; //把 name作为一个整体
cout<<name; //系统会自动在串尾加‘ \0?
这正是字符数组与其他数组的不同
§ 6.3 字符串
指向字符串的指针数组例:需要将计算机专业的所有课程名称存储起来共 24门课,每门课名称不超过 30个字符可以定义一个 24╳ 30的二维数组
const int max_sub=24;
const int max_len=30;
char subjects[max_sub][max_len];
§ 6.3 字符串
这种数据结构存在两个问题:
① 课程名不等长,会浪费内存空间
② 必须事先知道最长的课程名是多少字符
若用指针数组,可以避免这两个问题
const int max_sub=24;
char *subjects[max_sub];
char *subjects[]={“Object_Oriented Program\0”,
”Operation System\0”,”Network\0”,
“Datebase\0”,……};
§ 6.3 字符串
字符串指针数组的存储方式
.
.
.
.
.

Object_Oriented Program\0
Operation System\0
Network\0
Datebase\0
Software Engineering\0
subjects[0]
subjects[1]
subjects[2]
subjects[3]
subjects[4]
……
例,cout<<*subjects; //显示第一门课程名
cout<<*(subjects+2); //显示第三门课程名
§ 6.3 字符串
数组应用举例一个班有 30个学生,从键盘输入每个学生的名字和成绩,输出第一名学生的成绩和全班的平均成绩
#include<iostream.h> //学生名使用二维数组
void main()
{
const int STU_NUM=30;
char stu_name[STU_NUM][20];
int stu_score[STU_NUM];
int sum=0,highest=0;
float average;
for(int i=0; i<STU_NUM; i++)
cin>>stu_name[i]>>stu_score[i];
for(i=0; i<STU_NUM; i++)
{
sum+=stu_score[i];
if(stu_score[highest]<stu_score[i])
highest=i;
}
average=1.0*sum/STU_NUM;
cout<<"The highest student is:"<<stu_name[highest]<<endl;
cout<<"The highest score is:"<<stu_score[highest]<<endl;
cout<<"The average score is:"<<average<<endl;
}
//输入学生名和学生成绩
#include<iostream.h> //学生名使用指针数组
void main()
{
const int STU_NUM=30;
char *stu_name[STU_NUM];
int stu_score[STU_NUM];
int sum=0,highest=0;
float average;
for(int i=0;i<STU_NUM;i++)
cin>>*stu_name>>stu_score[i];
for(i=0;i<STU_NUM;i++)
{
sum+=stu_score[i];
if(stu_score[highest]<stu_score[i])
highest=i;
}
average=1.0*sum/STU_NUM;
cout<<"The highest student is:"<<*stu_name<<endl;
cout<<"The highest score is:"<<stu_score[highest]<<endl;
cout<<"The average score is:"<<average<<endl;
}
§ 6.4 指向对象的指针
对象指针
对象的动态创建与撤销
对象的复制与比较
§ 6.4 指向对象的指针
对象指针的定义类名 * 对象指针名;
或 类名 *对象指针名;
例,FILE* file_ptr;
file_ptr是对象指针,它只能指向 FILE类的对象
此时并未给对象分配内存,只有指针值的空间
§ 6.4 指向对象的指针
file_ptr=&file; //file是一个对象
只有当 file_ptr指向 file后才可以使用 file这个对象
使用对象指针访问对象对象指针 ->公有成员 ;
如,file_ptr->rename(……);
注:
① 只能访问对象的公有成员
② 不能用‘,?或‘ *’
§ 6.4 指向对象的指针使用对象指针的好处
减少内存空间的占用可以控制构造函数与析构函数的调用时间
使用灵活,方便
§ 6.4 指向对象的指针
对象的动态创建与撤销在程序运行过程中根据需要随时可以建立或删除对象动态创建的对象被存放在内存的,堆,

动态创建或删除对象时,需要两个运算符
new 和 delete
称为动态分配内存空间运算符
§ 6.4 指向对象的指针
new 的用法对象指针 =new 类名 (初始值表 );
表明在“堆”中创建一个属于“类名”的对象,
并用(初始值表)初始化被创建的对象若省略圆括号则被创建的对象使用缺省值
new运算符返回一个指针若 new不能分配到所需的内存,将返回 0,这时的指针为空指针
§ 6.4 指向对象的指针
用 new创建对象数组例,A* ptr;
ptr=new A[5];
new A[5]创建了一个对象数组,有 5
个元素,new的返回值赋给指针 ptr,使得 ptr指向对象数组
§ 6.4 指向对象的指针
用 new创建基类型的变量或数组例,int* p;
p=new int[10];
在程序运行中,动态申请了 10个整数空间
p是指向 int型变量的指针用 new创建了一个具有 10个元素的一维整型数组,并使 p指向这个数组的首元素注,用 new创建的数组不能指定初始值
§ 6.4 指向对象的指针
delete的用法
delete的作用用于删除使用 new创建的对象或一般类型的指针,并释放指针指向的内存空间
delete的用法
delete 指针名;
或 delete[ ] 指针名;
§ 6.4 指向对象的指针
例,A* ptr;
ptr=new A(5,6);
delete ptr;
注:
① 用 new创建对象时,调用构造函数用 delete撤销对象时,调用析构函数
② 用 new申请的空间必须用 delete释放用 new[ ]申请的空间必须用 delete[ ]释放
③ 在用 new[ ]创建对象数组的程序中,类中必须说明默认构造函数
§ 6.4 指向对象的指针
例:
char* str=new char[20]; //delete[ ] str;
float* fa=new float[100]; //delete[ ] fa;
short* sp=new short(5); //delete sp;
§ 6.4 指向对象的指针
new,delete应用举例例 1,用 new,delete创建和释放对象例 2,用 new,delete创建和释放普通数组请分析程序的输出结果例 1 分析程序的输出结果
#include<iostream.h>
class AA
{
public:
AA(int i,int j)
{
a=i;b=j;
cout<<"constructor.\n";
}
~AA()
{
cout<<"Destructor.\n";
}
void print();
private:
int a,b;
};
void AA::print()
{
cout<<a<<','<<b<<endl;
}
void main()
{
AA *a1,*a2;
a1=new AA(1,2);
a2=new AA(5,6);
a1->print();
a2->print();
delete a1;
delete a2;
}
例 2 分析程序的输出结果
#include<iostream.h>
#include<stdlib.h>
void fun()
{
int *p;
p=new int;
if(p)
{
*p=5;
cout<<*p<<endl;
delete p;
}
else
cout<<"Heap error!\n";
}
void main()
{
fun();
int* pa;
pa=new int[5];
if(!pa)
{
cout<<"Heap error!\n";
exit(1);
}
for(int i=0;i<5;i++)
pa[i]=i+1;
for(i=0;i<5;i++)
cout<<pa[i]<<" ";
cout<<endl;
delete[] pa;
}
§ 6.5 指向函数的指针
函数指针每个函数都有地址,指向函数地址的指针称为函数指针函数指针指向代码区中的某个函数通过函数指针可以调用相应的函数
§ 6.5 指向函数的指针
函数指针的定义
int (*func)(char a,char b);
其中:
int——函数的返回类型
*func—表示定义的是指针
() ——表示是函数
a,b——函数的两个字符型参数
§ 6.5 指向函数的指针
二者的区别
int (*func)(char a,char b);
int *func (char a,char b);
前者是函数指针的定义
后者是函数定义,函数的返回值为指针,
因为 ( )的优先级高于 *
§ 6.5 指向函数的指针
函数指针必须与它所指向的函数一致
(函数的返回类型和参数保持一致)
函数与函数指针操作的相互关系
int n1(char x,char y);
int *n2(char x,char y);
int n3(int a);
int (*p1)(char a,char b);
int (*p2)(int s);
P1=n1; //ok!
P1=n2; //error!
P2=n3; //ok!
P2=p1; //error!
P2=n3(5); //error!
§ 6.5 指向函数的指针说明:
函数指针与其它数据类型的指针不同函数指针指向内存的代码区
(程序运行的指令代码)
数据指针指向内存的数据区、栈区、堆区
(程序赖以运行的各种数据)
因此,两类指针不能相互赋值
§ 6.5 指向函数的指针
通过函数指针来调用函数函数及函数指针的定义
int n3(int a);
int (*p2)(int s);
必须给函数指针赋值
p2=n3;
通过函数指针来调用函数
p2(5);
函数指针用作函数参数(见下例)
例,计算以 0.1为步长,特定范围内的三角函数之和
#include<iostream.h>
#include<math.h>
double sigma(double (*pfunc)(double),double dl,double du)
{
double dt=0.0;
for(double d=dl; d<du; d+=0.1)
dt+=pfunc(d); //用函数指针调用函数
return dt;
}
void main()
{
double dsum;
dsum=sigma(sin,0.1,1.0);
cout<<"The sum of sin from 0.1 to 1.0 is:"<<dsum<<endl;
dsum=sigma(cos,0.5,3.0);
cout<<"The sum of cos from 0.5 to 3.0is:"<<dsum<<endl;
}