您的位置:首页 > 编程语言 > C语言/C++

C语言指针总结

2018-05-10 18:13 246 查看

起源

想学一下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()函数用了指针和解指针的方式巧妙的改变了
num1
num2
变量的值。

二、数组与指针

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

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大小对象的地址。他们两个都开始于同一个地址,所以
zippo
zippo[0]
的值相同

解指针可以用
[]
运算符,也可以用
**
运算符.
*zippo
等于
zippo[0]
**zippo
等于
zippo[0][0]


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

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

对于表达式1来说,
pz
指向了一个内含两个int类型值的数组。可能不太好理解,先来看
int *pz
int 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).num
temp->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);


关于指针的知识就总结完了,希望大家有所收获!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: