C语言--指针总结

起源

想学一下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);

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

猜你喜欢

转载自my.oschina.net/lizaizhong/blog/1810371