C语言指针总结

Wesley13
• 阅读 590

起源

想学一下UNIX系统编程,所以把C重学一遍,C中指针关联甚多,是重点也是难点。下面是自己做的一个总结,希望对你有所帮助!

一、何为指针

1、C的特殊性

C是一门很特殊的语言,特殊的地方在于它可能对计算机是友好的,对程序员并不太友好。
常识上来说,我们是不需要知道一个变量在内存中的实际地址的,对于我们实现业务没有意义。但早期编写C的这帮计算机科学家个个聪明绝顶,而且做得是底层研究,他们熟悉汇编,更熟悉计算机硬件原理,他们只是想做一个抽象层,编出一门语言来简化系统软件开发。
现在来看,对于学习JS、Java或是Python的人来说,C的编写过于复杂了,不够简洁。不过反过来看,我们也不能太苛刻,相比汇编语言来说,C已经好太多了。

2、初识指针

说到指针,它的定义就是:用于存储变量的内存地址。比如一个变量int num = 0;来说,代码执行的时候必然会给他分配内存,如果你是一个高端玩家,想要知道num储存在内存具体的那块地址上,那么可以用&num来获得实际地址。
执行代码:

    int num = 10;
    printf("%d  %p", num, &num);

执行结果大致为:

10  0060FF0C

那么获得一个内存地址有什么用呢?
我们先来看它在应用方面来讲最大的作用。

3、函数中变更变量的值

如果要实现两个int值互换,那么非常简单:

    int num1 = 10;
    int num2 = 20;
    int temp = num2;
    num2 = num1;
    num1 = temp;
    printf("%d %d",num1,num2);

如果要实现一个interchange函数呢,你可能想到:

void interchange(int num1, int num2){
    int temp = num2;
    num2 = num1;
    num1 = temp;
}

在main函数中调用:

int main(void){
    int num1 = 10;
    int num2 = 20;
    interchange(num1,num2);
    printf("%d %d",num1,num2);
}

打印出来的结果很有可能不符合预期哦,实际上两个数并没有进行交换。
这是怎么回事儿呢?这是由C语言的特性所决定的,num1和num2实际上只传递了值,传递了副本,所以可以这么看:在interchange()函数中的num1和num2已经是独立的值,已经和main()中的两个数没啥关系了,他们怎么改变也不会影响到main()中的num1和num2了。
说道这里,我们遇到了第一个问题,怎么解决呢?需要指针登场了!

4、指针的基础知识

先看下指针的基础知识,然后我们可以用这些知识来解决上述问题。
先有一个表达式:int num = 10;,指针的知识:

  • 获得一个变量的指针:&num
  • 指针声明:int * pnum = &num
  • 解指针:*pnum = num

这里容易让人迷惑的地方是指针声明和解指针用的都是*号。
简单来说,&num也是一个类型,是一个什么类型呢?指针类型。那么如何声明指针类型:int * pnum;pnum就是一个指针类型。
pnum是一个指针,那么我想获取它的实际表达值怎么办?也就是怎么解这个指针呢?也用到*号。所以*pnum其实和num是相等的。
如果感觉有点绕,请多读两遍。

5、利用指针解决问题

所以改良后的interchange()函数如下:

void interchange(int * num1, int * num2){
    int temp = *num2;
    *num2 = *num1;
    *num1 = temp;
}

调用:

interchange(&num1, &num2);

interchange()函数用了指针和解指针的方式巧妙的改变了num1num2变量的值。

二、数组与指针

定义一个数组,非常简单:

int powers[5] = {3,4,5,6,7};

下面开始引入数组与指针的关系了,如果你感觉魔幻,那么不是你的错,我刚开始也一样。
主要的规则有以下几点:

  • 数组名本身就是一个指针,是数组首元素的地址。也就是说*powers等于powers[0]
  • 指针+1,则指针的值递增它所指向类型的大小。也就是说powers+1等于&powers[1]
  • 对于指针来说,可以用++操作,比如powers++

多举几个例子,感受一下:

powers = &powers[0]
powers + 2 = &powers[2]
*(powers + 2) = dates[2]

1、函数形参、指针与数组

数组可以理解为是一种特殊的指针,所以下面的四种函数原型是等价的:

int sum(int *ar, int n);
int sum(int *,int);
int sum(int ar[], int n);
int sum(int [],int);

2、指针与多维数组

先看一个多维数组的定义:

int zippo[4][2];

对这个多维数组进行分析:

  • zippo[0]是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。他们两个都开始于同一个地址,所以zippozippo[0]的值相同
  • 解指针可以用[]运算符,也可以用**运算符.*zippo等于zippo[0]**zippo等于zippo[0][0]

下面开始烧脑的内容,请说明以下两个表达式的不同:

int (* pz)[2];  //1
int * pax[2];  //2

对于表达式1来说,pz指向了一个内含两个int类型值的数组。可能不太好理解,先来看int *pzint apz[2]有何联系,pz是一个指针类型,apz也是一个指针类型,而且他们都是指向int类型值的指针,所以这样赋值也不会有什么错误:pz=apz。通过这个实验,可以更加深刻的理解前面所说的数组可以理解为一种特殊的指针
回来看,int (* pz)=int pz[*],那么int (* pz)[2]=int pz[*][2]
那么对于表达式2呢,分解来看,pax[2]声明了一个包含了两个int类型值的数组,前面加上了指针声明的符号,所以int * pax[2]=int pax[2][*]

3、多维数组与函数

如果多维数组作为参数来传递,那么函数原型声明上也有要注意的一些地方,先来看正确的声明方式:

int sum(int (* ar)[4], int rows);
int sum(int ar[][4], int rows);

再来看错误的方式:

int sum(int ar[][], int rows);

为什么下面是错的呢,是因为编译器会把数组表示法转换为指针表示法,那么就必须知道ar所指向的对象大小。

三、字符串与指针

在C中,字符串是以空字符(\0)结尾的char类型数组。
那么我们很容易想到他的定义:

char mesg[11] = "hello world";
char * pmesg = "hello world";

根据前面数组的内容,这两种定义都是ok的。
那么他们有什么异同呢?还是说使用的时候任何情况下都可以看成是含义相同的?
这里又看出来C的复杂性,他们还是有很大差异的。主要的差异就是:char数组字符串声明的时候已经分配了指针空间的大小,在这里是sizeof(char) * 11;而char指针字符串没有分配具体的空间大小,这在某些情况下可能造成内存溢出。
可以先简单的记住这个规则:scanf()最好使用char数组字符串的声明方式。

四、结构与指针

C中的结构如:

struct node{
    int num;
};

跟Java中的Class相似。
这里要讲到结构指针,作用和前面的函数中变更变量的值差不多。
先思考一个问题,如果一个结构作为入参传入一个函数中,函数中修改结构的值会影响到main()函数中结构的值么? 可能答案你已经猜出来了,是不能的。这里还是要借助指针来实现:

void struct_demo(){
    struct node tn;
    tn.num = 1;
    struct_demo_swap(&tn);
    printf("%d",tn.num);
}
void struct_demo_swap(struct node * temp){
    temp->num = 10;
}

打印出来的结果是tn.num=10
注意,这里对于指针访问结构成员有两种方式:(*temp).numtemp->num

五、函数与指针

在C中,原始就支持JS的闭包或Java8的Lamda表达式,也就是支持函数作为参数来传递。
在C中,函数也有地址,指向函数的指针中存储着函数代码的起始处的地址。
一个规则:函数名可以用于表示函数的地址。看代码:

void ToUpper(char *);
void (*pf)(char *);
pf = ToUpper;

可以看到第二行声明了一个函数指针叫pf,这个函数指针定义了返回值和形参列表,只要是和他结构一样的,都可以赋值给他。这里pf = ToUpper;没有什么问题。
这样的规则会存在一些小问题:*pf实际上表示ToUpper函数,而pf和函数名又可以互换,所以(*pf)("abc")和pf("abc")、ToUpper("abc")又都是等价的。
下面看一个函数指针最常用的用法,就是作为入参:

void show(void (* fp)(char *), char * str);

关于指针的知识就总结完了,希望大家有所收获!

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这