亲爱的友友们,不知你们是否理解c语言的指针呢?
如果并不了解,或是有些许困惑,不妨来看看这篇文章吧,希望你能从中有所启发(手动比心)
目录
1.初识指针
1.1 举例——交换两数值
首先让我们来看一个程序——用函数实现交换a,b的值,这道题对于你们来说应该很简单,但是如果不熟悉指针的人可能会写出下面的代码:
#include <stdio.h>
void swap(int x, int y);
int main()
{
int a = 1;
int b = 2;
swap(a, b);
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
void swap(int x, int y)
{
int t = x;
x = y;
y = t;
}
乍一看好像没什么问题是吧?但是将程序运行起来,我们将会得到以下结果 :
可以看到,a,b的值并没有实现交换,所以为什么没有写成函数时一切正常,但是一使用函数就没办法交换了呢?
其实是因为函数在被调用的时候,形参是被临时创建的,一旦出了函数,它们就会被销毁。运行函数时将a,b的值赋给了x,y,实现了x,y的值的交换,然后函数调用结束,x,y就死了,本质上并没有对a,b造成任何影响。
由此可见,我们急需一个东西使得函数能够与实参建立联系,于是c语言就引入了指针,指针可以访问到想要访问的变量的地址,从而敲开变量家的门,交换两个变量家里的人。(是不是有点像高位者对低位者行使权力,形参和实参等级相同,都是int类型,所以无法通过该形参去改变实参)通过指针,我们就可以通过函数改变实参的值,所以将交换两数的值的代码进行改造:
#include <stdio.h>
void swap(int* x, int* y); //
int main()
{
int a = 1;
int b = 2;
swap(&a, &b); //
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
void swap(int* x, int* y) //
{
int t = *x; //
*x = *y; //
*y = t; //
}
结果就可以正确了 。
我将所有有变化的地方都用//进行标出,就会发现两个新的符号——“ * ”,“ & ”。
1.2 指针的相关符号 —— * ,&
取地址操作符(&)
在C语⾔中创建变量其实就是向内存申请空间,内存里面的每一个空间都有它们自己的地址,就像每一个人的家都有独一无二的坐标一样,家就是内存空间,而坐标就是地址,一个变量住进一个家,也就有了一个地址,而“&”(取地址操作符)就是可以获取变量地址的操作符。
例如:&a就获得了a的地址
地址表示
32位机器有32根地址总线(还有64位机器,这里以32位平台举例),每根线只有两态,表⽰0,1(电脉冲的有⽆),那么⼀根线,就能表⽰2种含义,2根线就能表⽰ 2x2=4 种含义,依次类推。32根地址线,就能表⽰2^32种含义,每一种含义就代表着一个地址,
我们知道,一个字节有8个比特位,所以存储一个地址就需要用32/8=4个字节(也就是一个指针的大小,因为指针存放的就是变量的地址)。
解引用操作符(*)
其实操作符“ * ”有两个用途:
1.定义一个指针变量
int a;是定义了一个整型的变量a。
int* b;就是定义了一个指向整型变量的指针。
b=&a;b就得到了a的地址
有多少不同类型的变量,就可以有对应不同类型的指针。char* a,double* a 等等。但是指针的类型必须和变量类型一一对应!因为不同类型的指针访问的权限不同,char*类型的指针解引用(就是下面的第二个用途)后只能访问char类型对应的一个字节,int*类型也是一样。
2.获取指针所指向变量的值(解引用)
int a=5;//定义变量a
int* p=&a;//p得到a的地址
*p=10;//通过解引用改变a的值
p得到a家的地址还不够,想要改变a的值就要通过“ * ”来进入a的家来改变a的值。
1.3指针+-整数
指针+-整数代表着指针指向位置的移动,移动的多少与指针类型有关(这也是指针类型与变量类型要一一对应的原因),char*类型指针+1代表跳过内存中的一个字节,指向下一个字节的地址,int*类型就是四个字节
要注意,在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的+-整数和解引用的运算,因为不知道要跳过几个字节。
1.4 const修饰指针变量
我们知道变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量,但是一旦这个指针变量用const修饰了(如:const char* p)编译器就不允许改变该指针存放的地址里面的值了!
1.5 野指针
所谓野指针就是没有明确指向的指针,它的成因有以下几种 :
1.指针未初始化
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
int main()
{
int arr[10] = {0};
*p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针
}
return 0;
}
3.指针指向的空间释放
int* test()
{
int n = 100;
return &n;//临时变量n出了函数后就被销毁了,其地址不复存在
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
避免野指针的方法:
1.定义时记得初始化指针,不知道初始化为多少就让它指向NULL。
2.访问数组时小心越界。
3.不再使用指针是及时置空NULL。
2.数组与指针
2.1数组名
有一个很重要的知识点,数组名是数组首元素的地址! 那为什么arr[0]就是数组第一个元素的值呢?其实[ ]与*有着相同的作用arr[0]等价于*(arr+0)!所以说数组名本质上也是一个指针!
不过在以下两种情况下数组名表示整个数组的地址:
1.sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
2.2一维数组传参
void test(int arr[], int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
arr[i] += 1;
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
test(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果 :
这里的参数也可以写作:
void test(int* arr)
因为本质上arr就是一个地址,所以我们就可以用一个指针来接收。
3.二级指针
指针可以存放一个变量的地址,而指针也是一个变量,那么存放一个指针地址该怎么做呢?
答案是使用二级指针:
int a = 5;
int* p1 = &a;
int** p2 = &p1;
这里的p2就是一个二级指针,通过*p2=某地址,就可以改变p1的值,也可以通过**p2=10,来改变a的值。(二级指针就是必一级指针更高一级的领导喽)
4.指针数组
指针数组就是一个数组(根据谁在后来判断,数组指针就是指针),是存放指针的数组,就像整形数组就是存放整型变量的数组一样。形如:
int *arr[10];
这个数组里面存放的就是十个int*类型的指针。由此我们可以联想到二维数组,如int a[3][5],其实就是存放了三个数组首元素的数组,每个数组有五个整型。
5.数组指针
顾名思义就是指向数组的指针,形如:
int (*p)[10];
就是一个指针指向一个数组,这个数组里有十个整型元素 。由于[ ]的优先级要⾼于*号,所以必须加上()来保证p先和*结合。
那么数组指针有什么用吗?这里就与二维数组传参联系起来了,二维数组可以看做几个一维数组的组合,二维数组的数组名是二维数组首元素的地址,即整个第一行数组的地址。所以想要用指针接收一个二维数组的数组名,就需要用到数组指针。如下所示:
void test(int (*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for(i=0; i<r; i++)
{
for(j=0; j<c; j++)
{
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
test(arr, 3, 5);
return 0;
}
6.函数指针
函数指针就是指向函数的指针,函数也是有地址的,要注意函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址。形如:
int Add(int x, int y)
{
return x+y;
}
int(*pf)(int, int) = Add;\\指针的类型为int(*)(int, int)
int(*pf)(int x, int y) = &Add;\\两种形式都可以
7.函数指针数组
把多个类型相同函数的地址存到⼀个数组中,那这个数组就叫函数指针数组。形如:
int (*parr[3])();
举个例子:
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
这里的add等就是函数。