指针和指针变量
指针的概念
- 内存中一字节称为一个存储单元,每个存储单元都有一个唯一的编号,称为地址。
- 变量的地址就是指向该变量所在存储区域的第一字节的地址。
- 地址就是指针。
指针变量的定义
指针作为地址,其实就是一个数值,为了区分地址数值和其他数值,特地给指针设计了一种变量,称为指针变量,用来存放内存地址。定义格式如下:
基类型 * 指针变量名
例如:
int *pi ;
基类型就是指针变量指向的数据的类型,比如一个指针指向的是一个char字符型变量,则指针变量的基变量就是 char 。
上例就是,定义 pi 为指向int型变量的指针变量;
不能给指针变量赋值为0,否则这就是一个无效指针;C++已经预先定义好了符号常量NULL,其值为0,通常用它来表示空指针值,此时该指针不指向任何量。
另外,地址值和整型量是不同的,若给pi赋值为100,则这个100的数据类型是int*,而非数值100,其数据类型为int。
变量pi也有自己的地址,这要和它存储的地址值区分开来!
指针运算符
& 取地址运算符
用来获取变量的地址,一般获取后需要赋值给一个指针变量;
* 取值运算符
用来访问指针指向的变量,也就是调取指向的变量的值,更通俗地说,就是代指,两者一心同体;
具体见下例:
#include<iostream>
using namespace std;
int main(){
int *p,m=200,n;
p=&m;//将m的地址赋值给指针变量p;
n=*p;//这里的*p其实就是m,此时m的值为200;
*p=100;//给变量m赋值为100;
cout<<"m="<<m<<endl;
cout<<"n="<<n<<endl;
return 0;
}
/*
m=100
n=200
*/
上例也体现了C++的直接访问和间接访问。其中直接访问就是通过使用变量名来直接调取变量的值;间接访问就是利用指针变量p和&m两者等价,*p和m两者等价的特性来对地址和数值进行操作,两者等价就是,你怎么变,我也怎么变的关系。
一般来说,不同类型的指针变量不能相互赋值,但可以将一个整型常量经过强制类型转换后赋值给指针变量,如下:
#include<iostream>
using namespace std;
int main(){
float *p;
p=(float*)1000;//其意义是,将整型量转为单精度浮点型后,赋值给指针变量p;
cout<<int(p)<<endl;//地址可转换为整型输出,这样输出的就是十进制数;
return 0;
}
在代码中出现星号有两种情况,第一种是定义指针变量时,将星号放在指针变量名前;第二种就是取值,将星号放在指针变量前,则整体代表指针所指向位置存储的值。
指针作为函数参数
本质上是传值调用,传递的是地址值;由于其特殊性,将它作为一种新的参数传递方式,所以,函数调用参数传递方式包括:传值调用、引用调用和传地址调用。
要注意的是,传地址调用虽然可以改变原来的值,但依然是单向传递;它看似为双向传递的原因是,在被调函数中使用通过参数传递来的指针间接访问主调函数中的变量,从而改变主调函数中变量的值。
指针和指向数组的指针
一维数组与指针
数组名
在C++中,数组名是数组存储区的起始地址;
int a [ 10 ] ;
如上,数组a的的起始地址就是 a ,也就是元素 a [ 0 ] 的地址,a是指针常量,其类型为 int * ,a与 & a [ 0 ] 等价;
可以通过指针变量来访问数组中的任意一个元素;
指针可进行的运算
指针一般可进行加、减、比较运算;
指针与正整型量的加减运算
int a[10] , *p2 , *p1 = a ;
p2 = p1 + 3 ;
在上例中,出现了指针与正整数的加法,其意义就是:p1加上3倍的基变量字节数,赋值给p2,p1指向的是a[0],p2则指向a[3] ;
int型的字节数为4,double型的字节数为8,char型的字节数为1;
由上可知,使用指针加减正整型量的运算,可以实现通过一个数组起始指针来访问数组中的任一元素,例如:
int a[10] , *p = a ;
for ( int i = 0 ; i < 10 ; i ++ ){
cin >> a [ i ] ;
//cin >> p [ i ] ;
//cin >> *( a + i ) ;
//cin >> *( p + i ) ;
上面四种写法都是合法的访问方式,在函数调用时也可以这样传递数组;
但要注意的是,p是指针变量,a是指针常量,所以p++是对的,a++是错的;
指针与指针的减法
指向相同类型变量的指针可以相减,其结果为两个指针所指的地址间数据的个数,就是数组元素下标相减的得数。
具体计算公式为如下:
数据个数 = (p1地址值 - p2地址值)/ sizeof(类型说明符)
指针的比较运算
指针可以通过比较运算符来判断两个指针是否指向同一变量或判断一个指针是否为空指针。
一维数组元素指针作为函数参数
引例 用函数实现将数组元素逆向存放。
#include<iostream>
using namespace std;
void reverse(int b[],int n){
int i=0,j=n-1,t;
while(i<j){
t=b[i]; b[i]=b[j]; b[j]=t;
i++;j--;
}
}
int main(void){
int a[10]={1,2,3,4,5,6,7,8,9,10},i;
reverse(a,10);
for(i=0;i<10;i++) cout<<a[i]<<" ";
cout<<endl;
return 0;
}
上例中,形参是一个数组和一个整数,其实就是传递原数组的起始地址给子函数,子函数处理后,原数组的值就改变了,而且这个参数数组不需要规定长度,程序会默认它是原数组的指针,就算规定了长度,也会被忽略。本质上这里的参数数组就是个指针。
利用指针来代替数组作为函数参数时,只要定义了*p,就可以用它代替a[ ],不失为一种好办法。
指针和字符串
字符数组和字符指针
字符数组名与字符指针的区别:
- 字符数组名 str :作为指针常量,不可再赋值;对字符数组使用复制函数 strcpy(str1,str2) 时,其复制的是数组的内容,而非str作为指针的指向;
- 字符指针 strp :作为指针变量,给它赋值一个字符串时即让它指向字符串的首字符地址;整个字符指针实际上就代表一个以该指针为起始地址、直到 '\0' 为止的一个字符串。
字符指针作为函数参数
同时字符指针也做实参,单向传递的是字符数组的首地址。
多种方法实现字符串复制函数
#include<iostream>
using namespace std;
void copy_string1(char to[],char from[])
{
int i=0;
while(from[i]!='\0')
{
to[i]=from[i];
i++;
}
to[i]='\0';
}
void copy_string2(char to[],char from[])
{
int i=0;
while((to[i]=from[i])!='\0') i++;
}
void copy_string3(char *to,char *from)
{
while((*to=*from)!='\0')
{
from++;
to++;
}
}
void copy_string4(char *to,char *from)
{
while((*to++=*from++)!='\0');
}
void copy_string5(char *to,char *from)
{
while(*to++=*from++);
}
int main(){
char s1[20],s2[20];
cin>>s2;
copy_string1(s1,s2);
cout<<s1<<endl;
return 0;
}
二维数组与指针
二维数组的地址
一维数组的存储是一条线,前后相连;二维数组的存储是由平行排列的线组成的面,每一行都是一个一维数组,前后相连,但是按列来看,只有最左边的第一列是上下相连的。这里的“相连”其实就是可以用数组名加上数字来表示元素。
数组名其实就是指针,在二维数组里,指针有两种,一种是专门表示第一列排头“大元素”的行指针。另一种则是表示每列中各个元素的元素指针 在行指针前加间接访问运算符 * 就可以得到元素指针,例如,定义了二维数组 a [ 3 ] [ 4 ] ,那么 a+i 是指向 a [ i ] 的行指针 ,* ( a [ i ] + j ) 是指向 a[i][j] 的元素指针。
表示二维数组元素 a [ i ] [ j ] 地址 | 表示二维数组元素 a [ i ] [ j ] 值 |
& a [ i ] [ j ] | a [ i ] [ j ] |
a [ i ] + j | * ( a [ i ] + j ) |
* ( a + i ) + j | * ( * ( a + i ) + j ) |
表示二维数组元素值还有第四种方式,就是先把 a [ i ] 看成 * ( a + i ) ,再在其后加上 [ j ] ,由于下标运算符的优先级比间接访问运算符的高,所以用括号把需要先间接访问的部分括起来,所以这样表示: ( * ( a + i ) ) [ j ] 。
行指针变量的类型
定义了二维数组 a [ i ] [ j ] ,则其中存在两种不同类型的指针,a [ i ] 的类型是 int* ,而 a 的类型则是 int ( * ) [ j ] 。a是行指针常量,依照其类型,可以推得行指针变量的定义方式:
<类型标识符> (* 行指针变量名)[ j ] ;
例如定义一个行指针变量p,即为 int (*p) [ j ] ; 此时可以将 a 赋值给 p ,通过 p 可以进行加减来获取所有数组里元素的地址和值。
二维数组名作函数参数
做形参时,行指针的写法有三种,分别是:int a [ 3 ] [ 4 ] 、int a [ ] [ 4 ] 、int ( * a ) [ 4 ] 。
将二维数组看成一维数组访问
二维数组虽然按逻辑来说是分为多行来进行二维存储的,但实际上在内存中还是线性存储,所以只需要将第一个元素指针赋值给指针变量p,加减后即可得到二维数组中的所有元素。
获得函数处理结果的几种方法
获得函数处理结果就是将被调函数的处理结果返回给主调函数。
- 利用return语句返回值,但是只能返回一个计算结果。
- 把变量定义在所有函数之外,使之成为全局变量,之后被调函数和主调函数中的变量就可以通用,不需要刻意传输。
- 利用指针变量作为函数参数得到近乎双向传递的效果。
- 利用引用 & 作函数参数来获取函数处理的结果。
指针数组
指针数组的定义和使用
基本概念
数组元素为指针就称为指针数组,若每个元素都为整型指针,则称为整型指针数组,类似地,还有float型指针数组,double型指针数组和char型指针数组。
指针数组的定义
<类型名> * <指针数组名> [ <元素个数> ] ;
指向指针的指针
因为指针本身也占用存储空间,所以存在指向指针的指针,其类型为 int **。
实际上还可以定义三级、四级等间接访问的指针变量,也即是 int ***p1 ,****p2 。
指针和函数
已经定义了函数之后,可以定义一个函数指针,就是把函数名换成(*指针名),这样将函数名赋值给指针后,函数指针就可以代替这个函数。
返回值为指针的函数称为指针函数,其定义格式如下:
数据类型 * 函数名 (参数列表){
函数体;
}·