1 一级指针
每8个bit,也就是1个字节,对应一个地址,也就是地址的最小单位是字节。
一级指针指向的内容一定是一个“地址”,而不是普通变量。
int a = 10;
int *p1;
p1 = &a;
上面的代码,我们没有在定义*p1的时候直接初始化赋值,而是在 下一行进行赋值,这说明了一个容易忽略的细节,那就是:
一级指针本身p1本身也是一个变量,它跟 a 是 一样的,只不过指向的内容不同而已,a指向的是一个具体的值,而p1则是指向a的内存地址。
我们对p1的赋值和各种其他操作(不带*),都是伴随着“内存地址”的。
指针指向的内容是个“内存地址”,指针本身也是一个变量,而不管是一级指针、二级指针 还是多级指针。内存只有一块,所有的 指针本身,也会分配一个内存地址,用于存放指针内容。
2 二级指针
2.1 二级指针基本概念
二级指针也是一个变量,它 指向的一定是“指针的地址”。对比下,一级指针指向的是普通变量的地址,二级指针指向的是“一级指针的地址”,以此类推,三级指针对应的是“二级指针的地址”。
2.2 二级指针的使用
2.2.1 函数的传值调用和传址调用
C中函数一般都有形参,当然也有形参为void的函数,对于那些有形参的函数,我们在调用的时候,就涉及到参数的传递,我们常见的参数有2种,一种是标准变量参数,比如int、char、结构体等单独变量,另一种就是指针,指针可以是一级指针,也可以是多级指针。
1)传值调用
函数的参数是标量参数,我们在调用函数,给函数传递参数时,是按照“传值调用”来运行函数的,这意味着函数将获得函数值的一份“拷贝”,注意这里说的是拷贝,这意味着我们可以放心的修改这个拷贝值,而不必担心会修改调用程序实际传递给他的参数。
2)传址调用
指针也是一个“值”,跟标准变量是一样的,只不过这个“值”规定好了,一定是一个“地址”,我们只能对地址进行简单的加减操作,多数情况下,我们通过对该地址值进行 引用操作,获取这个地址里的内容,所以,我们调用参数为指针的函数时,传递给函数的也是这个指针地址的拷贝,也就是所谓的“传递地址调用”。
void func(int *a)
{
*a = *a + 1;
}
int main(void)
{
int a = 5;
func(&a);
}
尽管在调用函数时,我们传入的是一个地址的值的拷贝,但是对“地址”的操作,就不是函数局部操作了,而是针对整个内存区域了,所以对该地址的间接操作(*)就是对这个地址指向的内存区域进行读写,所以这次的调用会改变a的值。
2.2.2 二级指针的作为函数形参
二级指针作为形参,往往是为了获取一个特定“地址”,是想要通过形参获取地址。这里可能会有些疑问,如果想要获取地址,那么直接将函数 返回值设定为指针型就可以啊,这 当然是可以的,但是还有一种 情况,比如我们想要获取多个特定地址,这个时候就需要使用二级指针了,比如我们想要我们想要从一串字符串中查找指定字符串,返回查找的字符串位置指针,同时在形参中也要返回(这么绕的目的是为了方便理解,)我们先给出错误的程序代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef unsigned char u8;
/*---------- 错误的使用案例!!! -----------------*/
u8 *func(u8 *src,u8 *cstr, u8 *str)
{
printf("before call str:%d\n", str);
str = strstr(src, cstr);
printf("end call str:%d\n", str);
return strstr(src, cstr);
}
int main(int argc, char *argv[]) {
u8 *src = "abcdefg123ef";
u8 *cstr = "cde";
u8 *str1 = NULL;
u8 *str2 = NULL ;
str1 = func(src, cstr, str2);
printf("str1:%s\n", str1);
printf("str2:%s\n", str2);
return 0;
}
程序执行结果如下:
before call str:0
end call str:4210727
str1:cdefg123ef
str2:(null)
这个错误的使用案例, func函数调用strstr库函数函数,返回值作为函数返回值,这样使用时没有问题的,但是如果我们将strstr的返回值赋值给str,那么就有问题了,原因如下:
调用func时,对于一级指针str,会进行“传址调用”,也就是我们会拷贝一个一模一样的数值到func中,而且本质上,指针 并不是什么特殊的东西,它也只是一个普通的变量,只不过这个变量里的内容是地址,这就相当于我们给一个拷贝后的值赋值一样,对原变量是没有任何改变的,所以我们在调用后,作为形参的str,内容并没有什么改变,当然如果想要改变,只需要对指针进行间接引用即可,为什么 间接引用可以呢?因为间接引用是通过拷贝的地址,读写地址指向的内容。
那么如何正确实现开头想要的目的呢?答案是,使用二级指针,正确的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef unsigned char u8;
u8 *func(u8 *src,u8 *cstr, u8 **str)
{
printf("end call *str:%d\n", *str);
*str = strstr(src, cstr);
printf("end call *str:%d\n", *str);
return strstr(src, cstr);
}
int main(int argc, char *argv[]) {
u8 *src = "abcdefg123ef";
u8 *cstr = "cde";
u8 *str1 = NULL;
u8 *str2 = NULL ;
printf("src addr:%d\n", src);
str1 = func(src, cstr, &str2);
printf("str1:%s\n", str1);
printf("str2:%s\n", str2);
return 0;
}
程序运行结果:
src addr:4210706
end call *str:0
end call *str:4210708
str1:cdefg123ef
str2:cdefg123ef
为什么使用二级指针就可以正确实现呢? 我们知道,一级指针 存放的是变量的地址,而二级指针存放的是一级指针的地址,而不管是几级指针,函数的调用,都是“传址调用”,所以假如形参是二级指针, 我们传入是二级指针的拷贝值,而二级指针的值是一级指针的地址,所以在上面的函数中,我在调用func函数时,一定是传入一级指针的地址,这是个巧妙的地方,不要直接定义一个二级指针,而是定义一级指针,传入一级指针的地址。
然后我们在函数内部,就可以对这个二级指针进行赋值,赋值为一个地址,这就相当于间接操作,我们不要直接给一级指针赋值,而是通过这个一级指针的地址间接给这个 一级指针赋值,这样很显然是合理巧妙的。
所以,归根结底,要特别留意,不要忽略掉函数引用的“传值调用” 和“传址调用”的前提。也 就是拷贝而非直接使用。
本文主要介绍了C语言中一级指针和二级指针的相关知识。一级指针指向普通变量的地址,本身也是变量。二级指针指向一级指针的地址。还阐述了函数的传值调用和传址调用,以及二级指针作为函数形参的使用方法和原理,提醒留意函数引用的前提。
551

被折叠的 条评论
为什么被折叠?



