460 likes | 612 Views
指针、引用与动态空间管理. 内容提要:. 1 、指针概述 2 、指针与数组 3 、指针与函数 4 、指针与字符串 5 、指针与自由空间 6 、引用的概念及应用. 指针、引用与动态空间管理. 指针概述. 一、指针的概念. 二、指针变量的赋值、初始化. 一、指针的概念. 1、访问变量的方法:直接法、间接法 直接法: 按变量的地址直接存取变量的方法。存贮变量的内存空间的首地址称为该 变量的地址 。
E N D
指针、引用与动态空间管理 内容提要: 1、指针概述 2、指针与数组 3、指针与函数 4、指针与字符串 5、指针与自由空间 6、引用的概念及应用
指针、引用与动态空间管理 指针概述 一、指针的概念 二、指针变量的赋值、初始化
一、指针的概念 1、访问变量的方法:直接法、间接法 直接法:按变量的地址直接存取变量的方法。存贮变量的内存空间的首地址称为该变量的地址。 间接法:如果将一个变量的地址放在另一个变量中,则存放地址的变量称为指针(Pointer)型变量。存取变量时值,可以间接地由指针变量取得该变量的地址对变量进行访问。 由于指针变量中的存放的是另一个变量的地址,习惯上形象地称指针变量指向该变量。指针变量中的值也简称为指针,所以指针就是地址,指针变量即存放地址的变量。 2、指针的类型: 指针类型即它指向的变量的类型。基本类型和非基本类型都有对应的指针类型,包括类(class),甚至还有指针类型(指向指针的指针,二级指针)。
3、指针变量的定义: 格式: 存贮类型 类型 *变量名 这里*说明后面的变量是指针变量。是而不是指针变量的一部分。 注意:在定义时每一个指针变量都需要一个指针变量说明符。例如: int *p1,*p2; 如果写成 int *p1,p2; 则编译器认为p2是整型变量,只有p1是指向整型变量的指针型变量。
4、与指针相关的运算符: 1)、 “&”:取地址运算符,对一个可寻址的数据(如:变量,对象和数组元素等等)进行操作,操获得该数据的地址。非左值。 2)、“*”间接引用(dereference)运算符,作用于一个指针类型的变量,求取指针变量的指向。左值。
二、指针变量的赋值、初始化 1、指针变量初始化: int age ; int *p_age=&age;//p_age初始化为指向整型量age 提示: 任何类型指针都可以赋以0值(NULL),这表示当前该指针并不指向该类型的任何一个变量(对象),并不是指向地址为0的内存空间。称空指针。 指针的类型可以强制转换,用作特殊用法。 例如: int m; int *pm=&m; char *p1=(char*)&m,*p2=(char*)pm; 用pm读的是整型数,用p1,p2读的是整型数的第一个字节。
2、指针赋值: 同类型的指针可以相互赋值,例如: int val1=8,val2=2, int *p_val1=&val1, int *p_val2=&val2; 则p_val1指向val1,p_val2指向val2,执行p_val1=p_val2后,则p_val1也指向val2,而没有指针指向val1了。 P_va11 va11 P_va11 va11 P_va12 va12 P_va12 va12 指针间赋值后的指针的变化
示例:指针赋值 float score; float *pf=&score; char ch; char *str=&ch; 下面的初始化语句是不对的。 float score; char ch; int *ip=&score;//不能将浮点变量的地址赋值给整型变量。 float *fp=&ch;//不能将字符变量的地址赋值给浮点变量 char * str=‘a’;//常数不是内存地址 int *ip2=&43;//不能对常数进行取地址操作
二、指针变量的赋值、初始化 提示: 对指针变量决不可以任意赋一个内存地址。 如:int *P=(int *)0xaf80; 把指针变量P的初始置为0xaf80,因为并不知道那个内存单元放的是什么数据,为谁所用,对其中的内容进行“写”操作是非常危险的。 *C语言的指针容易失控,在C++中增加了引用类型,它具有指针的主要功能,但限制了灵活性,使用更加安全。建议在函数参数传递中,能用“引用”时绝不用“指针”。
二、指针变量的赋值、初始化 3、指针常量: 指针常量是固定指向一个对象的指针,即指针本身是常量: char ch=’a’,ch1=’x’; char * const ptr=&ch; //注意const放在类型说明之后,变量名之前 *ptr=’b’; //正确 ptr=&ch1; //错误 ptr本身在初始化时所指向的地址是不可改变的,但它指向的目标ch的值是可以改变的。
二、指针变量的赋值、初始化 4、常量指针: 是指向“常量”的指针,即指针本身可以改指向别的对象,但不能通过该指针修改其所指的对象,常用于函数的参数,以免误改了实参。 char ch=’a’,ch1=’x’; const char * ptr1=&ch; //ptr1是常量指针 *ptr1=’b’; //错误,只能做ch=’b’ ptr1=&ch1; //正确 常量是不可寻址的,因此p_age=&20是错的。 但常变量是可寻址的,如: const float PI=3.14159; const float *pointer=Π//指向常量PI的指针必须是常量指针
指针与数组 数组名、指针和指针运算 指针与函数
数组名、指针和指针运算 1、数组名和指针的关系: • 数组名被看作该数组的第一个元素在内存中的首地址。 • 数组名在表达式中被自动转换为指向数组第一个元素的指针常量。数组名中所放的地址是不可改变的,所以称指针常量(即隐含说明“元素类型* const数组名”)。 • 数组名是指针,使用非常方便,但是却丢失了数组另一个要素:数组的大小。编译器按数组定义时的大小分配内存,但运行时对数组的边界不进行检测。这会带来无法预知的严重错误。
数组名、指针和指针运算 2、用指针访问数组: C++提供根据数组的存储地址访问数组元素的方法。例如:int fibon[4]; 则fibon是数组第一个元素的地址,*fibon是数组的第一个元素fibon[0],而fibon+1是数组第二个元素的地址,*(fibon+1)是第二个元素fibon[1]本身。指针加1,则地址移动一个数组元素所占字节数。 C++语言的下标运算符[ ]是以指针作为操作数的,fibon[i]被编译系统解释为*(fibon+i),即表示为fibon所指元素向后第i个元素。无论以下标方式或指针方式存取数组元素时,系统都是转换为指针方法实现。
数组名、指针和指针运算 3、指针的算术运算和关系运算: 1)、指针变量与整型量的加减表示移动指针,以指向当前目标前面或后面的若干个位置的目标。指针与整型量i的加减等于指针值(地址)与i*sizeof(目标类型)积的加减,得出新的地址。 *注意:运算结果并不表明那儿有一个指针所规定的数据类型的变量,这因为C++不对数组边界做检查。所以指针在使用时,必须十分小心。 2)、两个指针变量相减 只有当两个同类型的指针变量指向同一个数组时才可以进行减法运算,结果表示由第一个指针所指元素到第二个指针所指元素之间的元素数量。这里数量算头不算尾。 *注意:两个指针相加是毫无意义的。
数组名、指针和指针运算 3)、当且仅当两个同类型指针变量指向同一数组中的元素时,可以用关系运算符>,==,!=等进行比较,比较规则是指向后面元素的指针大,指向同一元素的相等。 4)、指针可以进行”++”,”--“运算,运算结果也是指向后一个或前一个数组元素。 注意:有”++”和”--”的指针表达式容易出错,必须小心使用,如: y=*pfib++; 该表达式的含义为:先求出pfib的指向,并赋值给变量y,而pfib自增1。
分析下面代码的运行效果: #include <iostream> void main(){ int i,f[10]={0,1,1,2,3,5,8,13,21,34},*pf1,*pf2; pf1=pf2=f cout<<"使用数组显示数列"<<endl; for(i=0;i<10;i++) cout<<f [i]<<'\t'<<pf1[i]<<endl; cout<<"使用指针显示数列"<<endl; for(i=0;i<10;i++) cout<<*(f+i)<<'\t'<<*pf2++<<endl; //注意:f++是错误的,因为f为常量,而pf2++是正确的 cout<<"显示指针相减,应为数组长度:"; cout<<pf2-pf1<<endl; //pf2已指向数组末尾 return 0;}
数组名、指针和指针运算 4、二维数组与指针: 二维数组是数组元素为一维数组的数组,所以等效的指针类型应该是指向一维数组的指针类型。如有: int x2d[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int (*pt)[4]=x2d; int a[4]={1,2,3,4}; int pa=a; 则指针pt 和x2d 是等效的。它们表示的首地址一样,所指目标类型也一样,pt 可以代替x2d,就象pa代替a一样。 指向二维数组的指针的定义如下: 数据类型 (* 指针变量名)[n]; 这里数组元素的个数n不可省略。因是指向指针的指针,称二级指针。
二维数组: 指向列方向 指向行方向 x2d[0][2] x2d[0][3] x2d[0] x2d[0][0] x2d[0][1] x2d(或pt) x2d[1] x2d[1][2] x2d[1][3] x2d[1][0] x2d[1][1] x2d+1(或pt+1) x2d[2] x2d[2][2] x2d[2][3] x2d[2][0] x2d[2][1] x2d+2(或pt+2) 二维数组分析 x2d指向的是一个由指针组成的数组,包括x2d[0],x2d[1]和x2d[2] 。由此可见,存储二维数组还必须保存一些访问数组元素的辅助信息,如:x2d[0],x2d[1]和x2d[2]。 在这里x2d即&x2d[0],即x2d中放的是第0行x2d[0]的地址,其所指目标是由4个整型数组成的一维数组,占内存16个字节。pt等效x2d,*pt即x2d[0]。
指向列方向 指向行方向 x2d[0][2] x2d[0][3] x2d[0] x2d[0][0] x2d[0][1] x2d(或pt) x2d[1] x2d[1][2] x2d[1][3] x2d[1][0] x2d[1][1] x2d+1(或pt+1) x2d[2] x2d[2][2] x2d[2][3] x2d[2][0] x2d[2][1] x2d+2(或pt+2) 数组与指针的等价关系
下面的代码用指向二维数组基本元素的指针变量和用指向组成二维数组的一维数组的指针变量输出二维数组全部基本元素。下面的代码用指向二维数组基本元素的指针变量和用指向组成二维数组的一维数组的指针变量输出二维数组全部基本元素。 int main( ){ int a[3][6]={{1,2,3,4,5,6},{7,8,9,10,11,12},{13,14,15,16,17,18}}; int * ptr,i,j; ptr=&a[0][0] ; //或 ptr = *a; 而不能ptr = a; for(i=0;i<18;i++){ cout<<*(ptr+i)<<'\t'; if(i%6==5) cout<<endl; } cout<<endl; int (* ptr1)[6]; //注意 ptr1是指向包含6个整型元素的一维数组的指针 ptr1=a; for(i=0;i<3;i++){ for(j=0;j<6;j++) cout<<*(*(ptr1+i)+j)<<'\t'; cout<<endl; } return 0;}
int main(){ int val=66; int *pval = &val; int **ppval = &pval; cout<<"val="<<val<<'\n'<<"**ppval=" <<**ppval<<'\n'; **ppval=18; cout<<"val="<<val<<'\n'<<"**ppval=“ <<**ppval<<endl; return 0;} 程序中ppval称为多级指针(即为指向指针的指针),val、pval和ppval之间的关系见下图: 变量ppval 变量pval 变量val
指针与函数 1、指针作为函数参数 C++中函数基本使用方法是传值调用。将指针用作函数的参数时,传的仍然是值,指针的值,这个值就是指针所指向的变量或对象的内存首地址,在物理上传的是指针的值,在逻辑上讲是把另一个变量的地址传过去了,可以看作传地址。
示例:用指针代作为参数实现两数据的交换 #include <iostream> using namespace std; void swap(double *d1,double *d2){ double temp; temp=*d1;*d1=*d2;*d2=temp; } int main(void){ double x,y; cout<<”请输入x和y的值”<<’\n’; cin>>x>>y; swap(&x,&y); cout<<”x=”<<x<<’\t’<<”y=”<<y<<endl; return 0;}
2、指针作为函数的返回值:函数的返回值也可以是指针。这样的函数称为指针函数。如希望返回多个值,用引用参数或指针参数来等效实现,如果希望返回一个数组,并且这个数组生命期不在该函数中消亡,可以返回一个指向该数组的指针。 示例:char *trim(char *s) { char *p=s+strlen(s)-1; while(p;s>=0 && *p==‘’)p--; *(p+1)=‘\0’; return s; } 该函数的功能是:截去参数字符串的尾部空格,并一字符指针的形式返回该字符串。
注意:指针函数所返回的指针不能指向函数返回后不存在的对象,如函数中的自动变量、形参变量。下面的做法是不可靠的。注意:指针函数所返回的指针不能指向函数返回后不存在的对象,如函数中的自动变量、形参变量。下面的做法是不可靠的。 示例: int *fun(int a,int b) { if(a>b) return &a; teturn &b; } 因为a、 b都是形式参数,其生命期只延续到函数 fun运行结束。
指针与字符串 在c++中对字符串的操作是通过数组来完成的,数组名代表数组首元素的地址。 下面的代码用两种方式实现字符串的拷贝。 void scopy1(char s[], char ct[]) { int i = 0; while ( ct[i] != ‘\0’){ s[i] = ct[i]; i ++;} s[i] = ‘\0’; } void scopy2(char *s, char *ct){ while(*ct != ‘\0’){ *s = *ct; s = s + 1; ct = ct + 1; } *s = ‘\0’;}
分析下面代码实现的功能 string make_lower(const string& s) { //所有大写改为小写 string temp(s); int i,s_length=s.length(); for(i=0;i<s_length;i++) temp[i]=tolower(s[i]); return temp;} void swap(char& ch1,char& ch2) { //交换两个字符 char temp=ch1; ch1=ch2; ch2=temp;} string reverse(const string& s){ //返回反转的字符串 int start=0,end=s.length(); string temp(s); while(start<end){ end--;swap(temp[start],temp[end]);start++; } return temp;}
string remove_punct(const string& s,const string& punct) { string no_punct; //放置处理后的字符串 int i,s_length=s.length(),p_length=punct.length(); for(i=0;i<s_length;i++){ string a_ch=s.substr(i,1); //单字符string int location=punct.find(a_ch,0);//从头查找a_ch在punct中出现的位置 if(location<0||location>=p_length) no_punct=no_punct+a_ch; //punct中无a_ch,a_ch拷入新串 } return no_punct; } //将第一个字符串中所包含的与第二个字符串中相同的字符删去
bool is_pal(const string& s) { //判断是否回文 string punct(",;:.?'\" "); //包括空格符 string str(make_lower(s)); str=remove_punct(str,punct);//滤去所有非字母字符 return str==reverse(str);} int main(){ string str; cout<<"请输入字符串,以回车结束。\n"; getline(cin,str); if(is_pal(str)) cout<<str<<"是回文。\n"; else cout<<str<<"不是回文。\n"; return 0;} 判断字符串是否是回文
指针与自由空间 1、基本知识 1)堆(heap): • 堆允许程序在运行时(而不是在编译时),申请某个大小的存储空间。 • 开辟堆内存的原因: • 一般情况下,定义数组的时候,数组的大小在程序编译时必须是已知的,如: • int i=10; • int a[i];//错误,数组的大小不能为变量 • int b[20];//正确 但是如果在定义数组时不知道数组应该定义多大,如果定义多了会浪费存储空间。因此,需要程序在运行时自动从系统中获取存储空间。 • 堆内存的实质 • 程序在编译和连接时不需要确定内存空间的大小,随着程序的运行自动的分配时大时小的内存空间,这种内存就是堆内存,所以堆内存是动态的,所以也称为自由空间(动态内存)。
2、动态内存申请 在c++中采用运算符new来实现 1)格式: new 类型名T[初值列表] 功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。 结果:成功:返回一个T类型的指针,指向新分配的内存。失败:0(NULL) 例: int arraysize; //元素个数,即需申明的空间大小 int *array; array=new int[arraysize]
3、动态内存释放 当空间不使用时,应及时释放。 1)格式 delete 指针 delete [ ]指针 2)功能 : 释放指针所指向的动态空间。 例如: int *p=new int; float *pf=new float[3]; delete p; delete[]pf;
注意事项: • new和 delete需要配套使用,如果搭配错了,程序运行时将会发生意想不到的错误 • 在用delete释放一个指针所指的空间时,必须保证指针所指的空间是用new 申请的,并且只能释放一次。 • 如果在程序中用new申请了空间,应该在程序结束前释放所有申请的空间。
例题:研读下面的代码,分析其运行结果 #include <iostream.h> struct date//定义结构体 { int month; int day; int year; }; int main() { int index, *point1, *point2; point1 = &index; *point1 = 77; point2 = new int; *point2 = 173; cout << "The values are " << index << " " << *point1 << " " << *point2 << "\n"; delete point2; point1 = new int; point2 = point1; *point1 = 999; cout << "The values are " << index << " " <<*point1 << " " << *point2 << "\n"; delete point1;
float *float_point1, *float_point2 = new float; float_point1 = new float; *float_point2 = 3.14159; *float_point1 = 2.4 * (*float_point2); delete float_point2; delete float_point1; date *date_point; date_point = new date; //动态分配结构体 date_point->month = 10; date_point->day = 18; date_point->year = 1938; cout << date_point->month << "/" << date_point->day << "/" <<date_point->year << "\n"; delete date_point; //释放结构体
char *c_point; c_point = new char[37]; //动态分配数组 delete [ ] c_point; //释放数组 c_point = new char[sizeof(date) + 133]; //动态分配数组 delete [ ] c_point; //释放数组 return 0; } Result: The values are 77 77 173 The values are 77 999 999 10/18/1938
4、用new创建多维数组 格式:new 类型名T[下标表达式1][下标表达式2]…; 如果内存申请成功,new运算返回一个指向新分配内存首地址的指针,是一个T类型的数组,数组元素的个数为除最左边一维外各位下标表达式的乘积。例如: char (*fp)[3]; fp = new char[2][3];
例题:研读下面的代码,分析其运行结果 #include<iostream.h> void main( ) { float (*cp)[9][8]; int i,j,k; cp = new float[8][9][8]; for (i=0; i<8; i++) for (j=0; j<9; j++) for (k=0; k<9; k++) *(*(*(cp+i)+j)+k)=i*100+j*10+k; //通过指针访问数组元素
for (i=0; i<8; i++) { for (j=0; j<9; j++) { for (k=0; k<8; k++) //将指针cp作为数组名使用, //通过数组名和下标访问数组元素 cout<<cp[i][j][k]<<" "; cout<<endl; } cout<<endl; } }
引用的概念与应用 1、引用的概念 引用(reference) 是c++ 语言中对一个变量或常量标识符起的别名。比如,已经定义一个变量val,然后建立这个变量的引用rval,就为变量val起了一个别名,val 和rval指向同一个变量。 2、引用的说明 格式:类型 & 引用名=所引用的对象 注意: 1)所引用的对象必须是已经有对应的内存空间。 2)引用一旦初始化,它就维系在所引用的目标上,不能改变,即不能将这个别名用作其他变量的别名。
示例:引用的简单应用 #include<iostream.h> void main() { int val; int &rval=val; cout<<“&val”<<&val; cout<“&rval”<<&rval; val=34; cout<<“val=“<<val; cout<<“rval=“<<rval; }
引用的概念与应用 2、能够引用的数据类型 并不是所有类型都可以引用 1)对简单类型变量或常量的引用是可以的、 如: int & char & float & double & bool &
2)对结构类型或常量的引用 如: student 是一个结构类型 student stu1={“wanglin”,45,6}; student &rstu1=stu1; 3)对指针变量或常量的引用 int *pint=new int ; int *rpint=pint; *rpint=90.1; dele rpint; 以上引用都是可以的
不能进行的引用: 1)对void 的引用 2)对数组的引用 3)指向引用类型的指针 3、const引用 1)当用const来限定引用时,表示不能通过引用改变被引用的空间的值。 2)对一个常量进行引用时,必须将这个引用定义为const
4、指针和引用 区别: 1)指针变量具有独立的内存空间存放变量的值,而引用只是一个依附于它所引用的变量的符号,没有独立的变量空间。 2)指针和引用对它们所指的或引用的变量的操作方式也不一样。指针通过运算符“*”来访问,而引用直接引用它所引用的空间。 3)指针本身是一个变量,它不一定要指向固定的内存空间。而引用的目标是固定的。 共性: 指针与引用都是对某一变量所代表的空间进行间接操作的改变。用指针作为函数的参数与引用作为参数,都能实现通过函数返回多个值。