C/C++中的关键知识点就是指针的用法,在代码开发过程中涉及到各种各样的指针。我结合自己的应用经验和查询相关说得比较清楚的资料信息,总结了一些基本的指针用法和理解。希望看完之后对指针的用法和理解会有更深入的理解。
1、指针变量
int *p1;
p1 是一个指向 int 类型数据的指针变量,至于 p1 究竟指向哪一份数据,应该由赋予它的值决定。再如:
int a = 10;
int *p_a = &a;
在定义指针变量 p_a 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p_a 就指向了 a。值得注意的是,p_a 需要的一个地址,a 前面必须要加取地址符&
,否则是不对的。
//定义普通变量
float a = 100.5, b = 100.6;
char c = 'a', d = 'as';
//定义指针变量
float *p1 = &a;
char *p2 = &c;
//修改指针变量的值
p1 = &b;
p2 = &d;
*
是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带*
。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*
,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*
,给指针变量赋值时不能带*
通过指针变量获取数据实例:
#include <stdio.h>
int main(){
int a = 15;
int *p = &a;
printf("%d, %d\n", a, *p); //两种方式都可以输出a的值
return 0;
}
打印输出的都是15,如果直接打印p,则输出的是15这个数据存储的地址
2、关于 * 和 & 的理解
假如int a =10;int *pa = &a。
假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a
和&*pa
分别是什么意思呢?*&a
可以理解为*(&a)
,&a
表示取变量 a 的地址(等价于 pa),*(&a)
表示取这个地址上的数据(等价于 *pa),绕来绕去,又回到了原点,*&a
仍然等价于 a。&*pa
可以理解为&(*pa)
,*pa
表示取得 pa 指向的数据(等价于 a),&(*pa)
表示数据的地址(等价于 &a),所以&*pa
等价于 pa
3、星号*
主要有三种用途:
- 表示乘法,例如
int a = 3, b = 5, c; c = a * b;
,这是最容易理解的。 - 表示定义一个指针变量,以和普通变量区分开,例如
int a = 100; int *p = &a;
。 - 表示获取指针指向的数据,是一种间接操作,例如
int a, b, *p = &a; *p = 100; b = *p;
。
4、数组指针
假设 p 是指向数组 arr 中第 n 个元素的指针,
即是:int arr[3] = {99,12,34}; int *p = arr表示p指向arr数组的第一个元素的地址
那么 *p++、*++p、(*p)++ 分别是什么意思呢?
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素,上面已经进行了详细讲解。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。
5、字符串数组和字符串常量
定义一个字符串有两种方法,见下面两种方法
char str[] = "this is a test str"
//char *pstr = "this is a test str"
//print mes
char *pstr = str;
int len = strlen(str), i;
//使用*(pstr+i)
for(i=0; i<len; i++){
printf("%c", *(pstr+i));
}
printf("\n");
//使用pstr[i]
for(i=0; i<len; i++){
printf("%c", pstr[i]);
}
//接输出字符串
printf("%s\n", pstr);
前面那种表示字符串的方法就是字符数据的表示方法
后面那只表示字符串的方法就是字符串常量表示方法
C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。
6、指针作为函数参数使用
函数的参数不仅可以是整数、小数、字符等具体的数据,还可以是指向它们的指针。用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。就像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合。
定义一个含有数组参数的函数
int max(int intArr[], int len){}
或者这样定义
int max(int intArr[6], int len){}
int intArr[]
虽然定义了一个数组,但没有指定数组长度,好像可以接受任意长度的数组。实际上这两种形式的数组定义都是假象,不管是int intArr[6]
还是int intArr[]
都不会创建一个数组出来,编译器也不会为它们分配内存,实际的数组是不存在的,它们最终还是会转换为int *intArr
这样的指针。这就意味着,两种形式都不能将数组的所有元素“一股脑”传递进来,大家还得规规矩矩使用数组指针。int intArr[6]
这种形式只能说明函数期望用户传递的数组有 6 个元素,并不意味着数组只能有 6 个元素,真正传递的数组可以有少于或多于 6 个的元素。需要强调的是,不管使用哪种方式传递数组,都不能在函数内部求得数组长度,因为 intArr 仅仅是一个指针,而不是真正的数组,所以必须要额外增加一个参数来传递数组长度。
C语言为什么不允许直接传递数组的所有元素,而必须传递数组指针呢?参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。对于像 int、float、char 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行内存拷贝有可能是一个漫长的过程,会严重拖慢程序的效率,为了防止技艺不佳的程序员写出低效的代码,C语言没有从语法上支持数据集合的直接赋值。
7、指针函数
C语言允许函数的返回值是一个指针地址),我们将这样的函数称为指针函数。下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个
#include <stdio.h>
#include <string.h>
char *strlong(char *str1, char *str2){
if(strlen(str1) >= strlen(str2)){ //不是说不能在函数里面求传进来的数组长度么?怎么理解?
return str1;
}else{
return str2;
}
}
int main(){
char str1[30] = "qqqqwwww", str2[30] = "qqqqwwwweeee", *str;
gets(str1);
gets(str2);
str = strlong(str1, str2);
printf("Longer string: %s\n", str);
return 0;
}
当然打印的值肯定就是qqqqwwwweeee
用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。
为什么说可能会出错呢?原因:前面我们说函数运行结束后会销毁所有的局部数据,这个观点并没错,大部分C语言教材也都强调了这一点。但是,这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。对于局部变量的指针地址和值如果在调用函数出来马上使用,可能还会存在而且数组正确,如果有其它函数被调用占用了该部分内存就会覆盖这块内存,得到的数据就失去了意义,会得到错误的数据。
8、函数指针(指向函数的指针)
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
实例
#include <stdio.h>
//返回两个数中较大的一个
int max(int a, int b){
return a>b ? a : b;
}
int main(){
int x, y, maxval;
//定义函数指针
int (*pmax)(int, int) = max; //也可以写作int (*pmax)(int a, int b)
printf("Input two numbers:");
scanf("%d %d", &x, &y);
maxval = (*pmax)(x, y);
printf("Max value: %d\n", maxval);
return 0;
}
运行结果:
Input two numbers:10 50↙
Max value: 50
第 14 行代码对函数进行了调用。pmax 是一个函数指针,在前面加 * 就表示对它指向的函数进行调用。注意( )
的优先级高于*
,第一个括号不能省略。
9、总结
指针(Pointer)就是内存的地址,C语言允许用一个变量来存放指针,这种变量称为指针变量。指针变量可以存放基本类型数据的地址,也可以存放数组、函数以及其他指针变量的地址。
程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。
定 义 | 含 义 |
---|---|
int *p; | p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。 |
int *p[n]; | p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]);,数组里面存的值都是地址 |
int *p(void); | p 是一个函数,它的返回值类型为 int *,这个函数叫指针函数 |
int (*p)(); | p 是一个函数指针,指向原型为 int func() 的函数。级int (*p) ()= func() |
1) 指针变量可以进行加减运算,例如p++
、p+i
、p-=i
。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。例如类型为int,那么++则增加4个字节,如果是char的话++就增加1个字节
2) 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;
是没有意义的,使用过程中一般会导致程序崩溃,不能是1000,必须是指向一个值的地址
3) 使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL
。
4) 两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数,更确切的说是相差的字节数,一个字节8位。
5) 数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。