C语言指针小结2

接C语言指针小结1,本文讲讲指针的一些进阶用法,数组指针,函数指针等。

assert断言

在此之前先讨论一下assert断言,这是一种可以检查野指针的宏,需要使用前包含头文件assert.h

#include <assert.h>
int main()
{
    int a = 10;
    int* p = &a;
    assert(p != NULL);
    return 0;
}

上面代码运行时,验证变量p是否等于NULL 。如果确实不等于NULL ,程序会继续运⾏,否则就会终⽌运⾏,并且给出报错信息提示。而如果assert返回值为真时,不会有任何事发生。

而如果我们不需要assert断言时可以直接在程序前定义宏

#define NDEBUG

此时运行程序便不会进行assert断言。

指针使用

当我们使用指针时,因为是指向变量的地址,所以改变指针便改变了原来的变量

#include <assert.h>
int main()
{
    int a = 10;
    int* p = &a;
    *p = 20;
    return 0;
}

此时a变量便被改成了20,当然只是这样的话,无非是脱裤子放屁多此一举,不如直接改变a向量就好,学习到现在,我感觉指针变量更好的用处便是在函数里的应用。

传值调用

首先是函数的传值调用,比较基础的一个用法

#include <assert.h>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 10;
    int b = 20;
    int c = add(a,b);
    return 0;
}

 add函数便是一个简单的加法函数,我们只需要把a和b的值传给他,然后他计算出a+b的值以后返回,我们用变量c储存结果。这个过程中我们并没有改变a和b变量的值,也不需要改变,所以直接进行传值调用。其实就是实参的值传给了形参,形参是实参的临时拷贝,计算完出函数销毁,传回计算结果。

传址调用

#include <assert.h>

void swap(int a, int b)
{
    int tmp = 0;
    tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a = 10;
    int b = 20;
    swap(a,b);
    return 0;
}

此时运行程序得到的a和b是没有交换彼此的值的,为什么呢?很简单,我们只是把a和b实参的值传给了形参a和b,形参在swap函数中交换了值,但出了函数以后形参销毁——对形参的改变不影响实参。

#include <assert.h>

void swap(int* a2, int* b2)
{
    int tmp = 0;
    tmp = *a2;
    *a2 = *b2;
    *b2 = tmp;
}

int main()
{
    int a = 10;
    int b = 20;
    swap(&a,&b);
    return 0;
}

经此一改,我们进行传址调用,把a和b的地址传给指针a2和b2,改变指针便改变了指针指向的变量,swap函数得以实现。

数组指针 

数组名的含义 

 

#include <assert.h>



int main()
{
    int a[5] = { 1,2,3,4,5 };
    
    return 0;
}

此时数组a单独拿出来,当我们直接用数组名a时,他代表数组的第一个元素的地址,即&a[0].

而有两个例外:1.当数组名单独放在关键字sizeof()中时,此时我们的数组名代表整个数组的大小,此处便等价于5*4=20.

2.当数组名加取地址符号时,此时代表整个数组取地址,&a为整个数组的地址。欸,那和第一个元素的地址有什么区别吗,答案是没有,都是指向一个地址,都是首元素的地址,但是我们进行地址的加减时,步长便会发生变化,此时的+1和-1都是整个数组的步长,比如此时的int(*p)[5] = &a,我们的p+1指向的地址便是数组中最后一个元素后面的地址,因为直接一步走过了整个数组的步长。

其实我们在C语言中对数组进行定义时:int a[5] = { 0 },[]也是操作符,操作数为a和5,而我们访问数组中的元素时,比如a[2]即3这个元素时,就是先找到数组首元素地址,然后往后数三个数组中元素大小的步长,找到这个元素的地址,然后解引用得到这个数。

 

#include <assert.h>



int main()
{
    int a[5] = { 1,2,3,4,5 };
    a[3] == *(a + 3) == *(3 + a) == 3[a];
    return 0;
}

定义数组指针 

对于数组指针的定义与普通的指针变量有一点区别

 

#include <assert.h>

int main()
{
    int a[5] = { 1,2,3,4,5 };
    int (*p)[5] = &a;
    *(p + i) == a[i];
    return 0;
}

我们在定义时,首先看他是指针,所以在指针变量p前加*代表他是指针变量,然后就是后面的[5]代表他是数组指针,指向的数组有5个元素,最后时前面的int代表数组指针指向的数组中的元素时int类型。

相应的二维数组的定义

#include <assert.h>

int main()
{
    int a[3][2] = { 1,2,3,4,5,6 };//三行两列的二维数组
    int (*p)[2] = &a;
    *(p + i)[j] = a[i][j];
    return 0;
}

这里有一点,二维数组其实就是几个一维数组的集合,也就是二维数组可以看成是一维数组,这个一维数组的元素都是一维数组,所以这个数组名便代表了数组中第一个一维数组的地址,此时一个元素的大小时一个一维数组的大小,便可以通过指针来实现对二维数组中每一个数的访问,先通过数组名加减确定行数,然后通过[]访问每个数。

函数指针

首先函数的&取地址与否与加不加&无关,即&function == function。

而对于函数指针的定义也和数组类似,1.先是一个指针变量2.函数参数类型3.函数返回类型。

 

#include <assert.h>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 10;
    int b = 20;
    int c = add(a,b);
    int (*p)(int, int) = &add;
    return 0;
}

指针数组 

顾名思义,存放指针的数组,定义的时候注意其实就是对数组的定义:

int * p[5] = { a, b, c, d, e };

就是一个放着五个指针的数组,元素的类型都是int*类型的指针变量。

函数指针数组

int (* a[2])(int, int) = {func1, func2}; 就是存放两个函数指针的数组,也是同样的道理。

只不过定义的时候我们把后面的a[2]放到了括号里,而数组里元素的类型是int(*)(int, int).

二级指针

简单来说就是指向指针的指针,有点套娃,但既是如此。因为指针变量他也是变量,变量的话就得在那内存申请空间存放他自己,那他就得有地址,我们便定义二级指针为指向这个指针变量的指针。

 

#include <assert.h>


int main()
{
    int a = 10;
    int b = 20;
    int *p = &a;
    int* *pp = &p;
    return 0;
}

其中第一个*和int一起代表我们的二级指针指向的变量是一个指针变量,类型是int*类型;而第二个*则表示这个变量pp 是一个指针变量。

qsort函数(数组排序函数)

首先给出qsort函数的定义:

void qsort( void* base, //指针,指向待排序数组的首元素
 size_t num,            //数组元素的个数
 size_t size,           //数组元素的大小(字节)
 int (*compar)(const void*, const void*)).  //比较函数,需满足当前面的变量大于后面的变量时返回大于0的数,等于返回0,小于返回小于零的数

这个函数是一个可以直接排序数组的函数,不论是数字数组还是字符数组(当然字符的话转化为ASCII码比较后排序),但使用时需要搭配比较函数compar来使用,当然这个函数需要我们自己写,通常数字的话可以直接return加减结果;而字符的话可以用strcmp函数比较(返回值正好满足compar要求,很完美)。

这里主要时函数指针的运用,还有回调函数,就是把函数指针作为参数传给另一个函数,当指针被用来调用其所指向函数的时候,被调用的那个函数我们叫做回调函数,在这里就是compar函数。

反思总结

比较笼统的介绍了一下指针,写到这有点晚所以最后的内容有点潦草,希望多多包涵。

日后有机会再精进提炼一下。

总的来说指针还是很单纯的,就是指向地址,但是衍生的东西较多,可能会产生混乱,但只需要按逻辑一步步来想,捋顺了逻辑,指针还是很好用的一个工具。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值