C语言学习之基础知识点—指针!上万字的干货知识

指针

指针是c语言里面最抽象的、最重要的、最常用的。

指针的概念:

指针变量也是一个变量,

指针存放的内容是一个地址,该地址指向一块内存空间,

指针是一种数据类型(指针类型)。

--------------------------------------

“我是一名从事了10年开发的老程序员,最近我花了一些时间整理关于C语言、C++,自己有做的材料的整合,一个完整的学习C语言、C++的路线,学习材料和工具。进企鹅裙学习()免费送给大家。这里是编程爱好者的聚集地,欢迎初学和进阶中的小伙伴。希望你也能凭自己的努力,成为下一个优秀的程序员。


计算机内存的最小单位是什么?BYTE(字节)

对于内存,每个BYTE(字节)都有一个唯一不同的编号,这个编号就是内存地址。

操作系统就给内存的每一个字节编了一个号,所以说:一个编号对应的是一个BYTE(字节)的空间大小。

打比方:

1 -> BYTE

2 -> BYTE

3 -> BYTE

4 -> BYTE

对应于

--------------------------------------

一个int多大?答:4个BYTE(字节),所以一个int占用了了4个编号(即4个不同的内存地址)。

地址的编号:在32位系统下是一个4个字节的无符号整数;在64位系统下是一个8个字节的无符号整数。

(因为地址不可能是负的,又因为无符号数可以表达一个更大的地址,有符号数表示的最大地址会变小)

-----------------------------------------------------------------------------

指针变量的定义:

可以定义一个指向一个变量的指针变量。

-----------------------------------------------------------------------------

取地址运算符 &

& 可以取得一个变量在内存当中的地址。(取地址取的是内存地址)

register int a;//寄存器变量,这种变量不在内存里面,而在cpu里面,所以是没有地址的,

所以寄存器变量不能使用&来得到地址。

-----------------------------------------------------------------------------

无类型指针

定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将 void * 转化为其他类型指针,

也可以用 (void *) 将其他类型强制转化为void类型指针。

void *p; 指针之间赋值需要类型相同,但任何类型的指针都可以赋值给 void * 。

-----------------------------------------------------------------------------

linux下示例代码如下:

int main()

{

int*p;//定义了一个可以指向int类型地址的指针变量,指针变量的名字叫p。*不是指针变量名字的一部分。

//int * 是一种数据类型。

inta;//定义了一个int类型的变量,int变量的名字叫a。

a =1;//int * 和 int是两种不同的数据类型。

p = &a;//把a的内存地址赋值给指针变量p。

printf("%p\\n", p);//0x7fff5b2faedc 输出的是a的首地址的编号,不会把四个编号都输出的。

//而且注意:每一次执行该代码后,输出的编号都会发生变化!

*p =10;//通过指针变量间接的访问a的值,*p代表指针指向变量的值,p代表指向变量的地址。

printf("a = %d\\n", a);//a = 10; 通过上面的方法把a的值改变了。

a =100;19printf("%d\\n", *p);//100 通过指针变量间接的访问a的值。

intb =2;

p = &b;//又把b的内存地址赋值给p。

*p =20;

printf("b = %d\\n", b);//20

//char *p1 = &a; //相当于 char *p1; p1 = &a;//两个类型不相同的地址。即指针类型不兼容。那么我们强转试试!

char*p1 = (char*)&a;

a =123456;

*p1 =0;

printf("a = %d\\n", a);//a = 123392 就算强转后也会出现问题,所以要避免指针类型不兼容问题。

void*p2;//可以指向任何类型的地址,void代表无类型。

return0;35}

-----------------------------------------------------------------------------

指针占用内存的说明

在同一个系统下,不管指针指向什么样类型的变量,地址的大小(或叫编号的大小)总是一样的。

linux下示例代码如下:

int main()

{

char*p1;

int*p2;

longlong*p3;

printf("%lu, %lu, %lu\\n",sizeof(p1),sizeof(p2),sizeof(p3));//实质是:编号的大小是多少?

return0;//输出的是 8, 8, 8

//地址的编号:在32位系统下是一个4个字节的无符号整数;在64位系统下是一个8个字节的无符号整数。

//指针变量的名字叫p1、p2、p3。指针变量的大小是多大呢?因为指针变量对应的是某某的首地址的编号,

//即指针变量对应的是编号,而编号就是内存地址。即编号在64位系统下是一个8个字节的无符号整数。

//所以指针变量的大小就是编号的大小,而编号在64位系统下用8个字节的无符号整数表示。

//举例子说明下:同一个酒店,房间的编号的长度都是一样的。

}

--------------------------------------

再比如:

linux下示例代码如下:

#include

int main()

{

int*p1;

inta =0;

p1 = &a;

*p1 =10;

//p1 = 10;

int*p2;

p2 = &a;

//*p2是什么?不管是*p1还是*p2都代表变量a的值,但p1和p2确实是两个不同的指针变量。

return0;

}

画图说明如下:


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

野指针 与 空指针

野指针:没有指向任何有效地址的指针变量,所以在代码中避免出现野指针,

如果一个指针不能确定指向任何一个变量地址,那么就将这个指针变成空指针。

linux下示例代码如下:

#include

int main()

{

int*p;

*p =100;//不能这样写,没有初始化过值的指针,这种指针叫野指针。

return0;//因为地址编号所占用的内存不是你程序要调用的内存。对于操作系统而言,不是你的内存你就不能改!

//如果你非要改的话,操作系统就会发现你在做非法操作,会直接把你清理出去了。即程序出错。

}

编译上段程序没有错误,运行上段程序会出现一个错误:Segmentation fault(段错误,也即分段故障)

-----------------------------------------------------------------------------

空指针:就是指向了NULL的指针变量。

linux下示例代码如下:

#include

int main()

{

int*p;//两句代码相当于一句:int *p = NULL;

p = NULL;//如果一个指针变量没有明确的指向一块内存,那么就把这个指针变量指向NULL。

//这个指针就是空指针,空指针是合法的。

//实际上NULL并不是c语言的关键字,NULL在c语言中的定义是:#define NULL 0

//NULL在c语言里面就是一个宏常量,值是0。那么我们为什么不直接写0呢?

//NULL代表的是空指针,而不是一个整数零,这样看的会舒服些。(这只是粗浅易懂的解释)

return0;

}

程序中不要出现野指针,但可以出现空指针。

--------------------------------------

空指针理解的扩展:

注意:

inta =0;

int*p = &a;//相当于 int *p; p = &a;

int*node = NULL;//相当于:int *node; node = NULL;

NULL就是系统定义特殊的0,把你初始化的指针指向它,可以防止“野指针”的恶果。

NULL是个好东西,给一出生的指针一个安分的家。

--------------------------------------

用C语言编程不能不说指针,说道指针又不能不提NULL,那么NULL究竟是个什么东西呢? C语言中又定义,定义如下:

#undefNULL

#ifdefined(__cplusplus)

#defineNULL 0

#else

#defineNULL ((void *)0)

#endif

所以我觉得,如果一个指针被赋予NULL,应该就相当于这个指针执行了0x0000这个逻辑地址,

但是C语言中0x0000这个逻辑地址用户是不能使用的,

(有些人说是因为0x0000没有映射到物理地址,也有人说是因为0x0000映射到的地址是操作系统用于判断野指针的,我也不太懂,总之就是用户不能使用啦)

所以当你试图取一个指向了NULL的指针的内容(或者叫值)时,就会提示段错误,听着有点绕,看程序:

1int*node = NULL;2inta =0;3a = *node;//*node的意思是:取指针变量node的值。然后赋值给a。45printf("%d\\n", a);

*node的意思是:取指针变量node的值,也就是逻辑地址0x0000,而这个地址是不能被访问的(即不能被取出来的),

c语言语法上没有问题,所以编译器编译没有问题,但是编译器编译后运行会出现段错误。

linux下示例代码如下:

#include

int main() {

int*p = NULL;//相当于 int *p = 0; 但一般不这么写啊!

inta =0;

a = *p;

printf("%d\\n", a);

return0;

}

root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p6 p6.c

root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p6

Segmentation fault(段错误,也即分段故障)

root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针#

=============================================================================

指针的兼容性(即指针类型之间一定要匹配)

指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个 double * 赋值给int。

#include

int main()

{

int*p;

charb =1;

p = &b;//指针类型之间一定要匹配,不然会有警告,强行运行的话,结果不可控!

return0;10}

警告如下:

warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]

警告:不兼容的指针类型分配[-Wincompatible-pointer-types]

=============================================================================

我们不要把指针想象的特别神秘!其实指针变量也是一个变量。

它里面放的就是一个地址的编号,地址的编号就是一个8个字节的无符号的整数(64位系统下)。

区别是:这个整数不能直接赋值,而是来自于对另外一个变量的取地址操作而得到!

=============================================================================

不同的数据类型在内存中占用的地址

我们先看几个现象:


C语言学习之基础知识点—指针!上万字的干货知识


每一次编译后执行,输出的地址会发生变化,但是相邻地址间的间隔不变。

--------------------------------------

再比如:


C语言学习之基础知识点—指针!上万字的干货知识


每一次编译后执行,输出的地址会发生变化,但是相邻地址间的间隔不变。

其余的类型就不一一举例啦!

=============================================================================

指向常量的指针 和 指针常量

const int *p; //定义一个指向常量的指针。

int *const p; //定义一个指针常量,一旦指向某一变量的地址后,不可再指向其他变量的地址。(注意:指针常量也叫常量指针

二者区别:

const int *p;//p是一个变量,但指向一个常量。(即p可以指向任何地址,但是只能通过*p来读这块地址的内容,不能通过*p来写这块地址的内容)

int *const p;//p是一个常量,但指向一个变量或者常量。(即如果一旦p指向了任何一个有效的地址后,就不可再指向其他变量的地址,但可以通过*p来读写这块地址的内容)

--------------------------------------

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

指针与数组的关系


C语言学习之基础知识点—指针!上万字的干货知识


--------------------------------------

一级指针画图小说明如下:


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

指针运算

指针变量可以进行计算,如果是 int * 类型每加一,变化4个整数;

如果是 char * 类型每加一,变化1个整数。其他类型以此类推。

linux下示例代码如下:

#include

int main()

{

inta =0;

int*p = &a;

printf("%p, %p, %p\\n", p, p +1, p +2);//0x7fff5c2a518c, 0x7fff5c2a5190, 0x7fff5c2a5194

return0;

}

-----------------------------------------------------------------------------

指针运算小例子:

linux下示例代码如下:

#include

int main()

{

inta[10] = {0 };

int*p = a;

p +=5; 9*p =1;

p -=2;12*p =3;

int i;

for(i =0; i <10; i++)

{

printf("a[%d] = %d\\n", i, a[i]);

}

return0;

}

输出的是:

a[0] =0

a[1] =0

a[2] =0

a[3] =3

a[4] =0

a[5] =1

a[6] =0

a[7] =0

a[8] =0

a[9] =0

-----------------------------------------------------------------------------

通过指针使用数组元素

linux下示例代码如下:

#include

int main()

{

inta[10] = {1,2,3,4,5,6,7,8,9,10 };

int*p = a;//指针p指向a的首地址。

p[3] =100;//等价于 *(p + 3) = 100; 一般写成左边那样。

printf("a[%d] = %d\\n", i, a[i]);

int i;

for(i =0; i <10; i++)

{

}

return0;

}

=============================================================================

不同类型的指针的区别以及与数组的关系

极端例子如下:

linux下示例代码如下:

#include

int main()

{

inta =0x12345678;

char*p = (char*)&a;

printf("%x, %x, %x, %x, %x\\n", *p, p[0], p[1], p[2], p[3]);//%x的意思是按照十六进制的有符号整数输出(小写)

printf("--------------------\\n");

*p =0;

p[3] =0;

printf("%08x\\n", a);

printf("--------------------\\n");

charb[20] = {0 };

int*p1 = (int*)&b;

p1[3] =0x12345678;

int i;

for(i =0; i <20; i++)

{

printf("b[%d] = %x\\n", i, b[i]);

}

printf("--------------------\\n");

return0;

}

输出的结果是:

78,78,56,34,12

--------------------

0034560033

--------------------

b[0] =0

b[1] =0

b[2] =0

b[3] =0

b[4] =0

b[5] =0

b[6] =0

b[7] =0

b[8] =0

b[9] =0

b[10] =0

b[11] =0

b[12] =78

b[13] =56

b[14] =34

b[15] =12

b[16] =0

b[17] =0

b[18] =0

b[19] =0

--------------------

说明:小端对齐输出。

小结:c语言中所有的数据类型都可以理解为一个char的数组。

-----------------------------------------------------------------------------

小案例:int类型与ip地址的对应关系。

实际上的ip地址是一个无符号的整数构成的。1个int,4个字节。

1、把整数转换为ip地址

ip地址的格式:

0.0.0.0 ~ 255.255.255.255

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

2、把ip地址转换为整数

输入一个ip地址

char a[100] = "192.168.2.5"

把这个ip转化为unsigned int类型的整数。

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

3、使用指针给二维数组排序

linux下示例代码如下:

#include

int main()

{

chara[2][5] = { {4,3,5,9,78}, {52,21,5,6,4 } };

char*p = (char*)a;

int i, j;

for(i =0; i <10; i++)

{

for(j =1; j <10- i; j++)

{

if(p[j] < p[j -1])

{

chartmp = p[j];

p[j] = p[j -1];

p[j -1] = tmp;

}

}

}

for(i =0; i <2; i++)

{

for(j =0; j <5; j++)

{

printf("%d\\n", a[i][j]);

}

}

return0;

}

输出的结果是:

3

4

4

5

5

6

9

21

52

78

=============================================================================

指针数组

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

linux下示例代码如下:

#include

int main()

{

int*a[10] = { NULL };//定义了一个指针数组,指针数组的名字叫a,每 个成员是int *类型的,一共有10个成员。

intb, c, d;//对于指针数组来说,要先有指针的性质,再有数组的性质, 即先得获得地址,然后对数组进行操作。

a[0] = &b;//a是指针数组的名字。

a[1] = &c;//a[0]是指针变量,

a[2] = &d;

*a[0] =10;//*a[0]是一个数组的成员之一。

printf("%d\\n", b);

return0;17}

=============================================================================

二级指针(指向指针的指针)

指针是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


二级指针说明图如下:


C语言学习之基础知识点—指针!上万字的干货知识


三级指针及其以上指针

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


linux下示例代码如下图所示:


C语言学习之基础知识点—指针!上万字的干货知识


特别注意:

能用一级指针解决的问题不要用二级指针,能用二级指针解决的不用三级指针,指针级数过多会导致程序很复杂。

工作中大量使用的是一级指针,二级指针也很常用,三级指针就很罕见了,四级指针几乎没有。但笔试会考你哦!可以画图解决!

=============================================================================

函数的参数为指针变量(指针变量作为函数的参数)

实际上指针更多的时候用在函数的参数上。

函数的参数可以使是指针类型。它的作用是将一个变量的地址编号传送给另一个函数。

void test(int *p); //定义一个函数,形参是int *类型。

c语言中如果想通过在一个函数的内部修改外部实参的值,那么就需要给函数的参数传递这个实参的地址。

-----------------------------------------------------------------------------

linux下示例代码如下:

#include

voidswap(inta,intb)//swap是交换的意思。

{

inttmp = a;

a = b;

b = tmp;

printf("a = %d, b = %d\\n", a, b);//a = 2, b = 1 形参中的值发生变化了。

}

int main()

{

inta =1;

intb =2;

swap(a, b);

printf("a = %d, b = %d\\n", a, b);//a = 1, b = 2 c语言中实参的值会传给形参,而形参值的改变并不会影响到实参。

return0;

}

----------------------------------------------------------------------------

那么现在我就想在一个函数的内部修改外部实参的值,那么就需要给函数的参数传递这个实参的地址。代码如下:

#include

voidswap(int*a,int*b)

{

inttmp = *a;

*a = *b;

*b = tmp;

printf("a = %d, b = %d\\n", a, b);//a = 2, b = 1 形参中的值发生变化了。

}

int main()

{

inta =1;

intb =2;

swap(&a, &b);//那么现在我就想在一个函数的内部修改外部实参的值,那么就需要给函数的参数传递这个实参的地址。

printf("a = %d, b = %d\\n", a, b);//a = 2, b = 1 实参中的值发生变化了。return0;

}

函数参数是指针变量的画图说明如下:


C语言学习之基础知识点—指针!上万字的干货知识


即:c语言想通过函数内部来修改实参的值,只能给函数传递实参的地址来间接的修改实参的值。

例如scanf函数:

int a;

scanf("%d", &a);//scanf是一个函数,现在要通过函数内部来修改实参a的值,只能用传递a的地址的方式修改a的值

=============================================================================

函数的参数为数组名时(即数组名作为函数的参数)

当一个数组名作为函数的形参的时候,c语言将数组名解释为指针变量,其实是一个指针变量名。

如果数组名作为函数的参数,那么这个就不是数组名了,而是一个指针变量名。

当把一个数组名作为函数的参数时,修改形参的值的时候,同时也影响实参的数组成员的值。

如果把一个数组名作为函数的参数,那么在函数内部就不知道这个数组的元素个数了,需要再增加一个参数来标明这个数组的大小。

如果将一个数组作为函数的形参进行传递,那么数组的内容可以在被调用函数的内部进行修改,

有时候不希望这样的事情发生,所以要对形参采用const进行修饰。

-----------------------------------------------------------------------------

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

函数的返回值为指针时(即指针作为函数的返回值)

int*test()//函数的返回值类型是指针类型(具体的讲解在下一节:内存管理)

{

return NULL;

}

=============================================================================

几个c语言的库函数:memset、memcpy、memmove函数,使用的时候需要包含头文件 #include <string.h>

这三个函数分别实现内存设置、内存复制、内存移动功能。

--------------------------------------

memset的功能是:将指定区域的内存置空(设置为0)。

void *memset(void *s, int c, size_t n);

第一个参数是:指定要置空的内存的首地址;

第二个参数是:要设置的值,一般写0;

第三个参数是:这块内存的大小,单位:字节。

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

memcpy功能是:两块内存之间拷贝数据。

使用memcpy时,首先一定要确保内存没有重叠区域。

void *memcpy(void *dest, const void *src, size_t n);

第一个参数是:目标地址(目标内存首地址);

第二个参数是:源地址(源内存首地址);

第三个参数是:拷贝多少内容,单位字节。

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


--------------------------------------

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


内存拷贝说明画图如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

memmove功能是:内存移动,参数与memcpy一致。

void *memmove(void *dest, const void *src, size_t n);

第一个参数是:目标地址(目标内存首地址);

第二个参数是:源地址(源内存首地址);

第三个参数是:拷贝多少内容,单位字节。

内存重叠区域说明如下图所示:


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

指针小结

定义 说明

int i; 定义个一个int类型的变量

int *p; 定义一个指向int类型的指针变量

int a[10]; 定义一个int类型的数组

int *p[10]; 定义一个指针数组,其中每一个数组元素指向一个int类型变量的地址

int func(); 定义一个函数,返回值类型为int类型

int *func(); 定义一个函数,返回值类型为int*类型

int **p; 定义一个指向int类型的指针的指针,二级指针

=============================================================================

字符指针 与 字符串

在c语言中,大多数的字符串操作其实就是指针操作。

--------------------------------------

1、通过指针访问字符串数组

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

通过指针使得字符串逆置

法一:使用一个指针

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


--------------------------------------

法二:使用二个指针

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

函数的参数为char *(即char *作为函数的参数)

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


如果将一个数组作为函数的形参进行传递,那么数组的内容可以在被调用函数的内部进行修改,

有时候不希望这样的事情发生,所以要对形参采用const进行修饰。代码如下:

voidtest(constchar*a)

{

printf("%s\\n", a);

//a[3] = '4';

}

-----------------------------------------------------------------------------

自定义函数实现求字符串长度和字符串拷贝

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

如果一个数组作为函数的参数,那么数组的成员数量在函数内部是不可见的。

解决方法:在传递一个数组的时候,需要同时提供另外一个参数,标明这个数组有几个成员变量。

例外:如果函数的参数是一个字符串时,那么并不需要再传递一个参数说明这个字符串有多长。

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


=============================================================================

指针数组作为main函数的形参

先来看一个指针数组作为函数的参数(此时把指针数组解释为二级指针)

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


输出结果是:

32,8

hello

h

abc

a

world

w

haha

h

-----------------------------------------------------------------------------

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


args是命令行参数的字符串数组,argc代表命令行参数的数量,程序名字本身就算一个参数。

main函数是由系统调用的,所以main函数的参数功能是:得到命令行的参数。

-----------------------------------------------------------------------------

举个小例子:用到main函数的参数,实现计算两个数的和

例如:

程序名 数1 数2

一回车结果就出来了。

a 15 45

60

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


-----------------------------------------------------------------------------

课后作业

写一个程序,需要用到main函数的参数

例如:

程序名 整数1 运算符 整数2,程序运行的结果是计算结果。

a 5 + 6注意:中间的加号是字符串。

11

a 5 * 6

30

......

+ - * /都要实现

linux下示例代码如下:


C语言学习之基础知识点—指针!上万字的干货知识


C语言学习之基础知识点—指针!上万字的干货知识


C语言学习之基础知识点—指针!上万字的干货知识



分享到:


相關文章: