(复习计划)--------C++程序设计(第三版)【谭浩强老师著】
第六章
目录
在程序中使用指针有许多优点,例如:提高程序效率;实现动态存储分配;可以通过调用函数,得到多个可改变的值etc.
接下来就是干货 ^ ^
- “ & ” 是 取地址运算符(&i 表示变量 i 的地址)
- “ * ” 表示 指向 (*a 表示 a 所指向的变量)
- 变量的地址称为该变量的指针。
- 用来存放变量地址的变量是指针变量。
下面两行语句作用相同
a=1;
*a=1;
//将 1 赋给指针变量 a 所指向的值
1.定义指针变量
//定义
int *point1 ; //指针变量名为 point
char *point2 ;
float *point3 ;
//赋值
point1 = &i; //将 i 的地址存放于指针变量point1中,i 为整型变量
point2 = &a; //a 为字符型变量
point3 = &b; //b 为浮点型变量
注意:
- 定义指针变量时必须指定基类型 (不同类型的数据在计算机中存储方式和占用的字节数是不相同的)
- 定义指针变量时要有 * 标识 (指向整型数据的指针类型表示为 “ int * ”,简称“ int 指针 ”)
- 不能用整数直接给指针变量赋值
int *a = 2000;
//这是错误的
- 一个指针变量只能指向同一类型变量
- 说明变量类型时要完整 (a是指向整型数据的指针变量 或 int* 型变量)
让我们现在来使用一下指针变量
#include <iostream>
using namespace std;
int main()
{
int a=2,b=3;
int *point1,*point2;
point1=&a; //把 a 的地址赋给了指针变量point1
point2=&b; //把 b 的地址赋给point2
cout<<a<<" "<<b<<endl;
cout<<*point1<<" "<<*point2;
return 0;
}
运行结果应为:
2 3
2 3
不难发现程序第11,12行,两个cout语句作用相同
2.对“&”和“*”运算符的说明
“ & ”与“ * ”的优先级别相同,同时出现时按照“自右向左”的方向结合
int *point1,*point2;
point1 = &a;
&*point1;
//*point1 就等于a, 所以&*point就相当于 &a
point2 = &*point1;
//相当于 point2=&a
*&a;
//*&a 和*point1 作用相同,都等价于变量 a
让我们来运用一下 (输入两个数,按从大到小顺序输出)
#include <iostream>
using namespace std;
int main()
{
int a,b,*point1,*point2 ;
int *p; //用来存放地址
cin >> a >> b ;
point1 = &a ;
point2 = &b ;
if(*point1 >= *point2)
{
cout<<*point1<<" "<<*point2;
}
else
{
p = point1;
point1 = point2;
point2 = p;
cout<<*point1<<" "<<*point2;
}
return 0 ;
}
3.用指针变量做函数参数
- 当指针类型做函数参数时,作用是 将一个变量的地址传给被调用函数形参
- 实参变量和形参变量之间的数据传递是“单向传递”,指针变量做函数参数时也不例外
- 用指针函数做参数时,调用函数不会改变实参指针变量的值,但可以改变实参指针变量所指向的值。利用这一性质,我们就可以通过调用函数,来改变主调函数中的变量的多个值。
我们来进行实际运用一下(输入两个数,按从大到小顺序输出)
#include <iostream>
using namespace std;
int main()
{
void change(int *a,int *b);
int a,b,*point1,*point2 ;
cin >> a >> b ;
point1 = &a ;
point2 = &b ;
if(*point1<*point2)
change(point1,point2);
cout<<*point1<<" "<<*point2;
return 0;
}
void change(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
但若函数写为下面的格式,就会出现错误
void change(int *a,int *b)
{
int *temp;
temp=a;
a=b;
b=temp;
}
//值传递只会单方向进行
Tip:若想通过调用函数来使若干变量的值发生改变,可以在主函数中为相应的变量创建指向它的指针变量(n个),编写函数时,其形参即为n个指针变量,在函数中改变其指向的值即可。
4.数组与指针
4.1通过指针访问一维数组
- 指针变量既然可以指向变量,当然也可以指向数组元素(把某个元素的地址放到一个指针变量中)
- 数组元素的指针就是数组元素的地址
int a[10];
int *p;
p = &a[0];
在C和C++中,数组名代表数组中第一个元素的地址(即序号为 0 的元素)
因此,下面两行代码等价
p = *a[0];
p = a; //把 a 数组首元素的地址赋给 p
要注意:数组名 a 并不能代表整个数组,这里只代表数组首元素地址
定义指针变量 (可以在定义时就赋初值)
int *p = &a[0]; //将a[0]的地址赋给p
int *p = a; //与上一行等价
此时可以对 p 所指向的数组元素赋值
*p = 10; //让 p 所指向的数组元素等于 1
- 若指针变量 p 已经指向数组中某一元素,则 p+1 指向该数组的下一个元素
int a[3] = {1,2,3};
int *p = a;
cout<<*p<<endl;
cout<<*(p+1)<<ednl;
cout<<*(p+2);
运行结果应为:
1
2
3
- 同样的,数组名 a 代表第一个数组的地址,a+1 也就代表第二个,即 a[1] 的地址
- 指向数组元素的指针变量也可以带下标,p[ i ] 与 *( p+i ) 等价
小结:
若 p 的初值为 &a[0] ,则:
- p+i 和 a+i 就代表 a[ i ] 的地址
- *(p+i) 和 *(a+i) 即为 a[ i ]
- 指向数组元素的指针变量也可以带下标,eg:p[ i ]=*(p+i)([ ],实际为变址运算符)
int a[10] = {0,1,2,3,4,5,6,7,8,9}; int *p = a; 1.以下表述(地址)等价 p+i; a+i; 2.以下表述(元素)等价 a[i]; p[i]; *(a+i); *(p+i);
我们来运用一下(输出数组中全部元素)
#include <iostream>
using namespace std;
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int *p; // int *p = a
p = &a[0];
for(int i=0;i<10;i++)
cout<<*(p+i)<<endl;
return 0;
}
//输出的其他格式(1)
for(int i=0;i<10;i++)
cout<<*(a+i)<<endl;
//(2)
for(int i=0;i<10;i++)
cout<<a[i]<<endl;
//(3)
for(int i=0;i<10;i++)
cout<<p[i]<<endl;
//(4)
for(p=a;p<(a+10);p++) //p 先后指向a[0]~a[9]
cout<<*p<<endl;
- 这里要注意(4),不能执行操作 “ a++ ”操作,因为实参数组名 a 代表一个固定的地址(或者说是指针常量),因此改变 a 的值是不可能的
//也可以自定义数组元素
for(int i=0;i<10;i++)
cin>>a[i]; //cin>>p[i]
//(2)
for(int i=0;i<10;i++)
cin>>*(a+i); //cin>>*(p+i)
C++编译系统将 a[ i ] 转换为 *(a+1) 进行处理,对每个 a[ i ] 都分别计算地址 a+i*d(d是一个元素所占的字节数) ,然后访问该元素
4.2用指针访问二维数组
4.2.1 指向元素的指针
int a[3][4]={{1,2,3,4},{2,3,4,5},{4,5,6,7}};
int *p =&a[0][0];
在这种定义下,对于元素的访问就需要一个一个向后推,假若想要访问 a[ i ][ j ]号元素,第 i 行 j 列元素实际上就是第 i*L+j 号元素(L为二维数组的列数),由4.1可知,*(p+i*L+j) = a[ i ][ j ]
4.2.2 指向行的指针
int a[3][4]={{1,2,3,4},{2,3,4,5},{4,5,6,7}};
int *p=a[0];
二维数组实际上是一个一维数组且每个元素都是一个一维数组,让 p 指向a[0],就相当于指向 a[0]的第一个元素,即a[0][0],若让 p 指向第 i 行,那么p +j 就是第 i 行 j 列 元素的地址了,即 *(p+j)=a[ i ][ j ];
5.用指针作函数形参接收数组地址
通过对(3.用指针变量做函数参数)的学习,我们知道用指针变量做形参时,也可以接收从实参传递来的数组首元素地址
我们来实践一下(将一组数按从小到大顺序排列)
#include <iostream>
using namespace std;
int main()
{
void pailie(int *a, int n);
int a[10];
int* p = a;
for (p = a; p < (a + 10); p++)
cin >> *p;
pailie(a, 10);
for (int i = 0; i < 10; i++)
cout << *(a + i) << " ";
return 0;
}
void pailie(int *a, int n)
{
void change(int *b, int *c);
int* q;
for (int i = 0; i < n; i++)
{
q = &a[i];
for (int o = i + 1; o < n; o++)
{
if (*q > a[o])
change(q, a+o);
}
}
}
void change(int *b, int *c)
{
int n = *b;
*b = *c;
*c = n;
}
我们发现,C++编译系统将形参数组名一律当作指针处理
因此以下两种写法完全等价:
void pailie(int *a, int b); void pailie(int a[],int b);//两种表述完全等价
( 这就清楚地说明:实际上在函数调用时并不存在一个占有存储空间的形参数组,只有指针变量)
6.字符串与指针
我们从定义一个字符指针开始
char *a;
char k[] =" kkJiaYou!";
a = k;
我们也可以直接对指针变量进行初始化
char *k = "kkJiaYou!";
这里直接用字符串变量 “kkJiaYou!” 对 k 进行初始化,实际上是把字符串中第一个字符的地址赋给 k
我们可以对其进行输出
char *k = "kkJiaYou!";
cout << k;
运行结果应为:
kkJiaYou!
- 在输出时,系统会先输出 k 所指向的第一个字符数据,然后使 k 自动 加 1 ,使 k 指向下一个字符并输出......直到遇到字符串结束的标志 ’\0‘ 为止
我们来练习一下(将字符串a1的内容,复制到字符串a2中去)
#include <iostream>
using namespace std;
int main()
{
char a1[]="kkJiaYou!",a2[20],*a,*b;
a = a1;
b = a2;
for(;*a!='\0';a++,b++)
*b = *a;
*b = '\0'; //输出b时可以自动结束
a = a1; //让指针指向的值变回首字母
b = a2;
cout<<a<<endl; //指针所指向的值一直在变化
cout<<b;
return 0;
}
7.函数与指针
- 指针变量也可以指向函数
- 一个函数在编译时被分配给一个入口地址,这个函数的入口地址就称为函数的指针
- 定义指向函数的指针变量的一般定义式为:函数类型 (*变量名) (函数形参);
- 形如 :int ( *p )(int , int ); char( *p )(char,char);
让我们来实际操作一下(把一组数进行从大到小排列)
#include <iostream>
using namespace std;
int main()
{
void paixu(int *a, int n);
void(* pai)(int*, int); //声明指针函数 pai
pai = paixu; //使 pai 指向函数 paixu
int a[10], * p;
p = a;
for (int i = 0; i < 10; i++)
cin >> p[i];
pai(p, 10); //调用函数
for (int i = 0; i < 10; i++)
cout << *(p + i)<<" ";
return 0;
}
void paixu(int *a, int n)
{
for (int i = 0; i < 10; i++)
{
for (int k = 0; k < n - i-1; k++)
{
if (*(a + k) < *(a + k + 1))
{
int v = *(a + k);
*(a + k) = *(a + k + 1);
*(a + k + 1) = v;
}
}
}
}
- 注意:定义函数的指针变量 pai 时,两侧的括号不可以省略,表示 p 先与 * 结合,是指针变量,然后再与后面的括号结合,表示该指针变量指向函数
8.返回指针的函数
- 返回指针的函数称为 指针函数 (调用后得到一个指向某类型数据的指针,即地址)
- 定义指针函数的一般形式为:类型名 *函数名 (参数列表);
- 形如:int *a (int x,int y) ; (要注意 a 左右没有括号)
9.指针数组
- 元素均为指针类型数据的数组,称为指针数组
- 数组中每一个元素都相当于一个指针变量,值为地址
- 一维数组定义式:类型名 * 数组名[数组长度];([ ] 比 * 优先级高 )
int (*a)[ 4 ]; 这是指向一维数组的指针变量
下面来应用一下 (将若干字符串按首字母顺序输出,从大到小)
#include <iostream>
#include <string>
using namespace std;
int main()
{
void sort(const char * a[], int n);
const char *name[] = {"Laowang","Xindi","Dage","Rongrong",
"Tianyu","Xiaodaun","Fage","Ernu"};
//在指针数组name中,8个元素的初值均为字符串的起始地址
sort(name, 8);
for (int i = 0; i < 8; i++)
{
cout << name[i] << endl;;
}
return 0;
}
void sort(const char* a[], int n)
{
const char* temp;
for (int i = 0; i < n - 1; i++)
{
for (int k = i + 1; k < n; k++)
{
if (strcmp(a[i], a[k]) < 0)
{
temp = a[i];
a[i] = a[k];
a[k] = temp;
}
}
}
}
输出数组也可以这样写 :
int i=0;
while(i<8)
{
cout<<*(name+i++)<<endl; //先求*(name+i),再进行i++操作
}
补充三种字符串函数(使用时必须加头文件 “#include <string> ”):
- 字符串连接函数:strcat( );
- 字符串复制函数:strcpy( );
- 字符串比较函数:strcmp( );
10.指向指针的指针
- 指向指针型数据的指针,简称为指向指针的指针
- 也可以这样理解:一个指针变量存放的又是另一个指针变量的地址,则称这个变量为指向指针的指针变量(指向指针的指针)
- 定义指向指针型数据的指针变量的一般形式:类型名 **变量名 (* 运算符的结合性是从右到左)
- 形如:int *(*p); 或 int **p;
int(*p),p 是指向整型数据的指针变量,现在(*p)前还有一个“ * ”,变为 int **p,就表示 p 指向的是整型指针数据,*p 就代表 p 所指向的整型指针
(指向字符型数据的指针变量)
#include <iostream>
using namespace std;
int main()
{
const char** p; //定义指向字符指针数据的指针变量 p
const char* name[] = { "Laowang","Xindi","Dage","Rongrong",
"Tianyu","Xiaodaun","Fage","Ernu" };
p = name + 2;
cout << *p << endl; //输出 name[2]指向的字符串
cout << **p; //输出 name[2]的字符串中的第一个字符
return 0;
}
运行结果应为:
Dage //*p 代表 name[2],指向字符串“Dage”
D //**p 是 *p 指向的“Dage”第一个字符的内容,即'D'
11.const 指针
11.1指向常量的指针变量
- 定义这种指针变量的一般形式:const 类型名 * 指针变量名(经定义后,不允许通过指针变量改变他所指对象的值)
- 常用于做函数的形参,以防止指针形参所指对象的值改变而影响实参
int a = 12, b = 15;
const int * p = &a;
*p = 15; //错误,不合法
//但直接改变 a 的值,是合法的
a = 15; //合法
虽然指针 p 所指向的值不能发生改变,但指针变量p的值是可以改变的:p = &b ;
(改变指针形参所指对象的值)
#include <iostream>
using namespace std;
void chan(int *a)
{
*a=5*(*a);
}
int main()
{
int a=10;
chan(&a);
cout<<a<<endl;
return 0;
}
输出结果为:
50
11.2常指针 (注意与指向常量的指针变量的区别)
- 常指针变量,简称常指针,定义这种指针变量的一般形式:类型名 *const 指针变量名
- 必须在定义时初始化,指定其指向
- 指向不能改变,但指向变量的值可以改变
int *const a = 12,b = 15;
*a = 15; //合法
a = &b; //不合法
11.3指向常量的常指针(把以上两种作用叠加)
- 定义这种指针变量的一般形式:const 类型名 *const 指针变量名
- 指针指向固定对象,该对象的值不能改变
int a = 12, b = 15;
const int *const p = &a;
p = &b; //不合法
*p = 15; //不合法
a = 15; //合法
*12.void指针类型
- 基类型为void的指针变量,即(void *)型变量
- 它不指向任何类型数据(可理解为 “指向空类型” 或 “不指向确定的类型” 的数据)可理解为“过渡型”
- 实际使用该指针时,要对他进行强制转换,使之适合于被赋值的变量类型
- 可以把非void型的数据直接赋给void型指针变量,但不能把void型指针直接赋给非void型指针变量,必须先进行强制转换
int a;
int *p = &a;
void *q;
q = (void *)p; //将 p 强转为void型,再赋给q
cout<<*q<<endl; //错误,q 不指向确定类型数据,不合法
cout<<*(int *)q; //把 q 强转为 int 类型,合法
注意:
q = p; //合法
p = q; //不合法
p =(int *)q;//合法
*13.引用(本质依旧为指针)
- 对一个数据可以建立一个 “引用” ,作用是 为一个变量起一个别名(是C++对C的一个重要扩充)
int a = 12;
int &b = a;
//声明 b 是 a 的 “引用”
//经过声明后 a || b 的作用相同,都代表同一变量
相当于:
int A = 12;
int *B;
B = &A; //B 为指向 A 的指针
- & 是引用声明符,并不代表地址
注意:
- 对于“引用”,只有声明,没有定义
- 声明一个“引用”的同时,必须同时初始化
- 在声明一个引用后,不能再使之作为另一个变量的引用
- 不能建立 引用数组/引用的引用/引用的指针
- 可以取引用的地址
- 要区别 引用声明符 和 地址运算符
(通过以引用为形参,实现两个变量的值互换(适用于C++) )
#include <iostream>
using namespace std;
int main()
{
void Change(int &,int &);
int A = 12,B = 15;
change(A,B);
//虚实结合,调用时a成了A的别名,b成了B的别名
cout<<A<<" "<<B;
return 0;
}
void Change(int &a,int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
运行结果为:
15 12
14.其他
- 指针变量可以有空值,即该指针变量不指向任何变量 (表示为:p = NULL;)
~the end.