指针
A 什么是指针:
在计算机中访问数据需要的一个东西叫做 地址:内存区的每个字节都有的一个编号。(区分:地址 & 地址对应的内容)
在内存中一般是通过变量名来对内存单元进行存取操作。但其实,程序编译后已经将变量名转换为变量的地址,对变量名的存取都是通过地址进行的。
直接存取(直接访问):按变量地址存取变量值的方式(变量名也算是变量地址的一种形式,属于直接访问)
间接存取(间接访问):将变量 i 的地址存放在另一个变量中。可以定义一种特殊的变量,它是用来专门存放地址的。
i_pointer=&i;
& 是取地址运算符,&i 是 i 的地址。地址指向该变量单元(通过地址就能找到该变量单元)。
因此,形象地称:地址为指针(通过它能访问以他为地址的内存单元)。一个变量的地址称为该变量的指针。对应,一个变量的指针就是变量的地址
一个专门用来存放地址(即指针)的变量,指针变量。
B 引用指针变量:
& 取地址运算符;
* 指针运算符(间接访问运算符)
&a 为变量a的地址,*p 指针变量p所指向的存储单元。
例子1:
#include <iostream>
using namespace std;
int main()
{
int a=100,b=233;
int *p1,*p2;
p1=&a;
p2=&b;
cout<<a<<' '<<b<<endl;
cout<<*p1<<' '<<*p2<<endl;
return 0;
}
在程序的第6行最然定义了两个指针变量 p1 和 p2 ,但是它们并未指向任一个整形变量,而是只提供两个基类型为整型的指针变量。(用 int* 定义)
& 和 * 的优先级别相同,按从右而左方向结合。故:& * p1和 & a 相同,都是和“ a 变量的地址” 等价;* & a 与 a 等价。
例子2:
输入a和b两个整数,按先大后小的顺序输出 a 和 b (用指针变量处理,不交换整形变量的值,而是交换变量指针的值, a 和 b 的内容并未交换。)
#include <iostream>
using namespace std;
int main()
{
int a,b;
cin>>a>>b;
int *p1,*p2;
p1=&a;
p2=&b;
if(a<b)
{
int *p=p1;
p1=p2;
p2=p;
}
cout<<*p1<<' '<<*p2<<endl;
return 0;
}
C 用指针作函数参数
函数的参数也可以是指针类型,它的作用是:将一个变量的地址传送给被调用函数的形参。
例子3:
输入a和b两个整数,按先大后小的顺序输出 a 和 b (要求用函数处理,并且用指针变量作为函数参数。)
#include <iostream>
using namespace std;
int main()
{
void pai(int *p1,int *p2);
int a,b;
cin>>a>>b;
int *p1,*p2;
p1=&a;
p2=&b;
if(a<b)
{
pai(p1,p2);
}
cout<<a<<' '<<b<<endl;
return 0;
}
void pai(int *p1,int *p2)
{
int t;//形参指针p1和p2是无法通过void函数改变的
t=*p1;//所以这里通过地址(虚)访问并改变参变量(实)的值,虚实结合
*p1=*p2;
*p2=t;
}
注:1. 本次选择了交换整形变量的值而非指针变量。p1依旧指向 a ,而p2也依旧指向 b 。
2. 由于虚实结合的方式是采取单向的“值传递”的方式,只能从实参向形参传递数据,形参质的改变无法传递给实参。
而通过把指针作为形参,实现了:通过调用函数使变量的值发生变化,在主调函数中使用这些改变了的值。
3. 和任意其他数据类型一样,不能通过改变形参指针变量的值而使实参指针变量的值改变。
4. 总结2、3两条,调用函数时不会改变实参指针变量的值,但可以改变指针变量所指向变量的值。
5. 函数的调用可以(而且只可以)得到一个返回值(即函数值),而使用指针变量作函数参数,就可以通过指针变量改变主调函 数中变量的值。相当于通过函数调用从被调用的函数中得到多个值,如果不用指针变量是难以做到的。
例子4:
输入a和b和c三个整数,按先大后小的顺序输出 a 和 b 和 c(要求用函数处理,并且用指针变量作为函数参数。)
#include <iostream>
using namespace std;
int main()
{
void pai(int *p1,int *p2);
int a,b,c;
cin>>a>>b>>c;
int *p1,*p2,*p3;
p1=&a;
p2=&b;
p3=&c;
if(a<b) pai(p1,p2);
if(b<c) pai(p2,p3);
if(a<b) pai(p1,p2);
cout<<a<<' '<<b<<' '<<c<<endl;
cout<<*p1<<' '<<*p2<<' '<<*p3<<endl;
return 0;
}
void pai(int *p1,int *p2)
{
int t;//形参指针p1和p2是无法通过void函数改变的
t=*p1;//所以这里通过地址(虚)访问并改变参变量(实)的值,虚实结合
*p1=*p2;
*p2=t;
}
D 数组与指针
指针既然可以指向变量,也就当然可以指向数组元素。所谓数组元素的指针就是数组元素的地址。
p=&a[0];
p=a;//在c++中这两个语句等价
注:在第二行代码中数组名 a 并不代表整个数组。第二行代码的作用是:把 a 数组的首元素的地址赋给指针变量 p ,而绝非把 a 数组各个元素的值赋给p。
可以通过指针引用数组元素:
*p=1;//对p当前所指向的数组元素赋予数值1
ps:1. 若果指针变量p指向数组中的一个元素,则 p+i 指向同一数组中的下一个元素。(p+i 所代表的地址实际上是 p+i*d,d是一个 数组元素所占用的字节数)
2. 数组名(如 a )代表是的数组首元素的地址,a+i 也是地址。和 p+i 同理。指针法(能使目标程序质量高,即占内存少,运行 速度快。)
3. a[3] 数组名后的方括号“[ ]”,实际上是变址运算符。下标法
例子1:
下标法表示数组。
#include <iostream>
using namespace std;
int main()
{
int a[10];
for(int i=0;i<10;i++)
{
a[i]=i+1;
}
for(int i=0;i<10;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
例子2:
指针法表示数组。
#include <iostream>
using namespace std;
int main()
{
int a[10];
for(int i=0;i<10;i++)
{
*(a+i)=i+1;
}
for(int i=0;i<10;i++)
{
cout<<*(a+i)<<' ';
}
return 0;
}
例子3:
指针变量法表示数组。
#include <iostream>
using namespace std;
int main()
{
int a[10];
int *p=a;
for(int i=0;i<10;i++)
{
*(p+i)=i+1;
}
for(int i=0;i<10;i++)
{
cout<<*(p+i)<<' ';
}
return 0;
}
注:1. 例子1、2 对每个a[ i ](或“*(a+i)”)都要计算地址,然后再访问元素。而例子3 是用指针变量直接指向元素,不必每次都重新计算地址。例子3 的效率和执行速度都是三个中最高的(但没有下标法直接)。
2. 注意数组越界问题!
用指针变量作为函数形参接受数组地址:
数组名代表数组首元素地址。用数组名做函数参数传递的是数组首元素所对应的地址。因此,用指针变量作函数形参,同样可以接受从实参传来的数组首元素地址(此时,实参是数组名)。
例子4:
将10个整数按由小到大的顺序排列。
#include <iostream>
using namespace std;
int main()
{
void pai(int *p,int n);
int a[10];
int *p=a;
for(int i=0;i<10;i++)
{
cin>>*(p+i);
}
pai(a,10);
for(p=a;p<(a+10);p++)
{
cout<<*p<<' ';
}
return 0;
}
void pai(int *p,int n)
{
for(int i=0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
if(*(p+i)>*(p+j))
{
int t=*(p+i);
*(p+i)=*(p+j);
*(p+j)=t;
}
}
}
}
注:1. c++编译系统将形参数组名一律按照指针变量来处理。
2. 实际上在函数调用时并不存在一个占用存储空间的形参数组!
3. 实参数组名(如 a )代表一个固定的地址,或者是说是指针形常量,因此要改变 a 的值是不可能的!
4. 而形参数组名是一个指针变量,并非一个固定的地址。因而它可以可以改变的。比如在函数开始时,就接受了实参数组首元素地址,在函数执行过程中,它也可以被赋值。
E 字符串和指针
在c++中有三种方法可以访问一个字符串。
例子1:
用字符数组来存放一个字符串。
#include <iostream>
using namespace std;
int main()
{
char str[]="I love HNU!";
cout <<str;
return 0;
}
例子2:
定义一个字符串变量并初始化,然后输出其中的字符串。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str="I love HNU!";
cout <<str;
return 0;
}
例子3:
定义一个字符串指针变量并初始化,然后输出它指向的字符。
#include <iostream>
using namespace std;
int main()
{
char *str="I love HNU!";//注意这里要用char型!
cout<<str<<endl;
cout<<*str;//这样的话就只会输出字符串首元素,即‘I’
return 0;
}
注:1. 第五行,在对字符指针变量初始化时,实际上是把字符串中的第一个字符的地址赋给 str。
2.在输出时(第六行),系统先输出 str 所指向的第一个字符数据,然后使 str 自动加一,使之指向下一个字符,然后在输出一 个字符。。。如此,直至遇到字符串的结束标志为止。
3. 在内存中字符串的最后被自动加了一个 ‘\0’ ,因此能在输出时确定字符串的终止位置。
4. 利用字符串指针来进行两个字符串(字符数组形式)的赋值,是相当痛苦的,要一位一位的赋值。(推荐直接用string型)
E 函数与指针
指针变量也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个函数的入口地址就是函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。函数名代表函数的入口地址。
例子1:
求 a 和 b 中的大者。(用一个指向 max 的指针变量来调用该函数)
#include <iostream>
using namespace std;
int main()
{
int max(int a,int b);//函数声明
int (*p)(int a,int b);//定义指向函数的指针变量p
int a,b;
cin>>a>>b;
p=max; //使p指向函数max
int m=p(a,b);
cout<<m;
return 0;
}
int max(int a,int b)
{
if(a>b)
{
return a;
}
else
{
return b;
}
}
注:语句 int (*p)(int a,int b); 中,依次的意义为,第一个int:指针变量所指向的函数的类型;(*p):p 是指向函数的指针变量;(int,int):p所指向的函数中的形参的函数类型。
在定义指向函数的指针p时,(*p)两侧的括号不能省略,表示 p 先与 * 结合,在与右面的括号结合。如果省略的话,该语句的含义就变成了:定义一个返回值是指向 int 型的指针的函数!
在定义完对应函数的指针变量后,还要进行一部操作:使指针 p 指向函数max。来使函数的入口地址赋给指针变量p。
F 返回指针的函数
简称为指针函数。在定义时,注意和定义指向函数的指针作区分。
int *p(int x,int y,int z);
G 指针数组和指向指针的指针
指针数组:一个数组其元素都为指针类型。指针数组的每一个值都相当于一个指针变量,它们的值都是地址。
例子1:
将若干字符串按字母序排序输出。
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
void sort (char *name[],int n);
char *name[]={"bnji","fnhij","c++","phbj","co0"};
int n=5;
sort(name,n);
for(int i=0;i<n;i++)
{
cout<<name[i]<<endl;
}
return 0;
}
void sort (char *name[],int n)
{
for(int i=0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
if(strcmp(name[i],name[j])>0)
{
char *t=name[i];
name[i]=name[j];
name[j]=t;
}
}
}
}
注:排序函数sort的形参是指针数组明,实参是指针数组name的首元素地址。排序改变的是数组的不同元素所指向的字符串(即指针数组的值——地址)。
指向指针(数据)的指针。就比如刚才的指针数据name,数组名name就是指针数组首元素的地址。所以name以及(name+ i )
例子2:
指向字符型数据的指针变量。
#include <iostream>
using namespace std;
int main()
{
char **p;//定义指向指针的指针变量p
char *a[]={"aa","bb","cc","dd","ee"};//定义指针数组
p=a+2;
cout<<*p<<endl;//输出name[2]指向的字符串(通过其首字母地址)
cout<<**p; //输出name[2]指向的字符串的首字母
return 0;
}