从零到一看指针(C语言)

亲爱的友友们,不知你们是否理解c语言的指针呢?

如果并不了解,或是有些许困惑,不妨来看看这篇文章吧,希望你能从中有所启发(手动比心)

目录

1.初识指针

1.1 举例——交换两数值 

1.2 指针的相关符号 —— * ,&

1.3指针+-整数

1.4 const修饰指针变量 

1.5 野指针 

2.数组与指针

 2.1数组名

2.2一维数组传参

 3.二级指针

4.指针数组

5.数组指针 

 6.函数指针

7.函数指针数组


 

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;
}

乍一看好像没什么问题是吧?但是将程序运行起来,我们将会得到以下结果 :

b3ccf86377894c27a283c080829d7c5d.png

 可以看到,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*类型就是四个字节

eacc73693e88461982f724a63ad56e71.png

要注意,在指针类型中有⼀种特殊的类型是 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;
}

运行结果 :

47533ab0a07d4c5ba173b4a2766b5dc3.png

这里的参数也可以写作:

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],其实就是存放了三个数组首元素的数组,每个数组有五个整型。 

52143cf95094463d9a30617b6cd4770a.png

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等就是函数。 

 

 

 

 

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值