深入浅出C语言指针技巧与总结

更新:11-15 民间故事 我要投稿 纠错 投诉

今天给各位分享深入浅出C语言指针技巧与总结的知识,其中也会对进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

1 指针的定义

我们知道普通变量存储一个值。指针变量还存储一个值,但这是一个特殊值:它的值是另一个变量的地址

指针的定义形式如下:

数据类型*名称;

或者

数据类型*名称=值;这意味着name是一个指针,它指向dataype类型的地址。

指针存储一个地址。如果需要获取该地址对应的内容,可以通过解引用字符*获取:

整数a=12;

int *pa=a;

printf("*pa:%u.", *pa); //输出为12;

*pa=14; //此时a的值为14,这里需要注意的一点,也是我以前经常困惑的一点:

定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给一个字符串常量进行初始化

例如:

整数*a;

.

*a=12;上面的代码片段说明了一个极其常见的错误:我们声明了这个变量但从未初始化它,因此无法预测值12 将存储在哪里。如果变量是静态的,它将被初始化为0;如果变量是自动的,则根本不会初始化。在任何一种情况下,声明指向整数的指针都不会“创建”用于存储整数值的内存空间。

但是,以下定义创建一个字符串常量(为其分配内存):

char *p="面包果";初始化指针时创建的字符串常量被定义为只读。如果尝试通过指针修改该字符串的值,程序将出现未定义的行为。

除上述定义正确外,其他定义均错误:

浮动*点=3.14; //错误!编译失败

2 指针的运算

指针+(-)整数

指针存储的是一个地址,它本质上是一个整数,因此可以对整数进行加法或减法。但这不是普通的加法或减法。添加或减去指向整数的指针会产生另一个指针。但运算后指针指向哪里呢?当一个指针和一个整数执行算术运算时,整数在执行加法(减法)运算前会根据合适的大小进行调整。这个"合适的大小"就是指针所指向类型的大小,"调整"就是把整数值和"合适的大小"相乘。#includeint main()

{

整数a=10;

int *pa=a;

双b=99.9;

双*pb=b;

字符c="@";

字符*pc=c;

printf("sizeof(int)=%u, sizeof(double)=%u, sizeof(char)=%un",

sizeof(int), sizeof(double), sizeof(char));

//初始值

printf("a=%p, b=%p, c=%pn", a, b, c);

printf("pa=%p, pb=%p, pc=%pn", pa, pb, pc);

//加法运算

帕++;铅++;电脑++;

printf("pa=%p, pb=%p, pc=%pn", pa, pb, pc);

//减法运算

pa -=2;铅-=2;个人计算机-=2;

printf("pa=%p, pb=%p, pc=%pn", pa, pb, pc);

返回0;

}运行结果:

sizeof(int)=4,sizeof(double)=8,sizeof(char)=1

a=000000000061FE04,b=000000000061FDF8,c=000000000061FDF7

pa=000000000061FE04,pb=000000000061FDF8,pc=000000000061FDF7

pa=000000000061FE08,pb=000000000061FE00,pc=000000000061FDF8

pa=000000000061FE00, pb=000000000061FDF0, pc=000000000061FDF6 从上面的结果可以看出,当指针pa、pb、pc 加1 时,实际地址增加了对应类型的大小。减法也是如此。

指针-指针

仅当两个指针都指向同一数组中的元素时,才允许将一个指针与另一个指针相减。两个指针相减的结果是两个指针之间的元素数量。例如,如果p1指向array[i],p2指向array[j],则p2-p1的值就是j-i的值。如果两个指针指向的元素不在同一个数组中,则它们相减的结果是未定义且无意义的。

3 指针与数组

3.1 数组指针(指向数组的指针)

数组指针,它是一个指针,指向一个数组。也就是说,它存储的是数组变量的地址。所以指针每增加一步就是数组的长度。由于它在每一步都跨越整个数组,因此也称为行数组。

#includeint main()

{

int a[3][4]={{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};

int (*pa)[4];

pa=a;

printf("a:%p, a:%p, a[0][0]:%pn", a, a, a[0][0]);

printf("pa:%p, (*pa)[0]:%un", pa, (*pa)[0]);

帕++;

printf("a[1]:%p, a[1][0]:%pn", a[1], a[1][0]);

printf("pa:%p, (*pa)[0]:%un", pa, (*pa)[0]);

返回0;

}运行结果:

a:000000000061FDE0, a:000000000061FDE0, a[0][0]:000000000061FDE0

pa:000000000061FDE0, (*pa)[0]:1

a[1]:000000000061FDF0, a[1][0]:000000000061FDF0

pa:000000000061FDF0, (*pa)[0]:5 首先,pa是一个数组指针。它首先存储数组a的第一个元素的地址。由于数组名也是数组的首地址,因此a、a、a[0][0]具有相同的地址。该地址也存储在pa 中。然后解引用pa,然后获取数组,然后(pa)[i]获取数组中索引为i的元素。

3.2 指针数组

指针数组,它本质上是一个数组,只不过整个数组是以指针类型存储的。

#includeint main(无效)

{

char *p1="Himanshu";

char *p2="阿罗拉";

char *p3="印度";

字符*arr[3];

arr[0]=p1;

arr[1]=p2;

arr[2]=p3;

printf("n p1=[%s] n",p1);

printf("n p2=[%s] n",p2);

printf("n p3=[%s] n",p3);

printf("n arr[0]=[%s] n",arr[0]);

printf("n arr[1]=[%s] n",arr[1]);

printf("n arr[2]=[%s] n",arr[2]);

返回0;

}运行结果:

p1=[希曼舒]

p2=[阿罗拉]

p3=[印度]

arr[0]=[Himanshu]

arr[1]=[阿罗拉]

arr[2]=[印度] 参考:https://www.thegeekstuff.com/2012/01/advanced-c-pointers/

4 指针与字符

在C语言中,表示字符串的形式一般有两种,一种是数组的形式,另一种是字符指针的形式。

数组形式:

char arr[]="hello,world";字符指针形式:

char *str="你好,世界";虽然上面两种形式都可以表示字符串,但是它们还是有一些区别:

存储方式字符数组由若干个元素组成,每个元素存储一个字符,字符指针变量只存储字符串的首地址,而不是整个字符串。存储位置

数组是在内存中创建的用于存储字符串的空间,并且存储在堆栈区域中。字符指针在字面常量区开辟一块空间来存储字符串,并将字符串的首地址付给指针变量str。赋值方式

对于数组,下面的赋值方法是不正确的:char str[10];

str"你好"; //错误!对于字符指针变量,可以使用下面的方法来赋值:

字符*a;

一个="你好";可否被修改

指针变量所指向的字符串的内容不能被修改,但是指针变量的值(即存储的地址或指针)可以被修改。

5 指针与结构体

可以指向结构类型的指针称为结构指针。结构体的成员中还可以有指针成员。

结构体{

字符*名称; //姓名

整数; //学生号

年龄; //年龄

字符组; //团体

浮动分数; //分数

} Stu1={ "汤姆", 12, 18, "A", 136.5 }, *pstu=Stu1;上面的代码中,定义了一个结构体变量stu1。这个变量中有一个指针变量名。还定义了结构体指针pstu。

如果我们想通过结构体指针访问结构体成员,一般有两种方式:

(*pstu).名称;

pstu-名称;

6 指针与const:常量指针与指针常量

参考:https://www.thegeekstuff.com/2012/06/c-constant-pointers/

https://eli.thegreenplace.net/2003/07/23/const-with-pointers的正确用法

初学者经常会误解这两个概念。首先,我想你需要明白这里的常量意味着什么。常量就是只可读不可修改。那么常量指针和指针常量哪一个是只可读不能修改的呢?是指针还是指针指向的东西?

这里有一个方法可以让你快速了解哪些是不能修改的。即声明时以星号(*)为界,将其分为两部分,星号左边的部分和星号右边的部分。无论const 在哪里,都只能读取而不能修改。

以下面的代码为例,我们来分析一下:以星号(*)为界,星号左边是char,并且没有const关键字,所以它指向的内容不是常量。然后,我们看到星号右边是const ptr,所以我们可以说ptr是一个常量。因此,这行代码声明了一个指针,它是常量,但它指向的内容不是常量。也就是说,这是一个指针常量。

char* const ptr="只是一个字符串";同样,我们也可以分析下面的代码:

//数据和指针都不是const

//

char* ptr="只是一个字符串";

//常量数据,非常量指针

//

const char* ptr="只是一个字符串";

//常量指针,非常量数据

//

char* const ptr="只是一个字符串";

//常量指针,常量数据

//

const char* const ptr="只是一个字符串";

6.1 指针常量(Constant Pointers)

指针常量(Constant Pointers) : 其本质是常量,只不过这个常量是一个指针。由于指针是只可读不可修改,所以这个指针不能指向其他地址,但是这个地址中的内容仍然可以改变。

指针常量的声明格式如下:

* const 例如: int * const ptr;我们看下面的程序:

#includeint main(无效)

{

int var1=0,var2=0;

int *const ptr=var1;

ptr=var2;

printf("%dn", *ptr);

返回0;

}在上面的程序中:

我们首先定义了两个变量var1,var2;然后,我们定义了一个指针常量ptr并将其指向var1。然后,我们尝试让ptr指向var2。最后我们打印出了指针ptr指向的地址的内容。让我们运行这个程序:

main.c: 在函数“main”: 中

main.c:6:9: error: 只读变量“ptr”的赋值

ptr=var2;

^我们看到该程序在尝试为只读变量ptr 赋值时编译并报告错误:因此,一旦我们定义了一个指针常量,这个指针就不能指向其他变量。

但我们仍然可以修改指向的地址:中的内容

#includeint main(无效)

{

int var1=0;

int *const ptr=var1;

*ptr=10; //好的

printf("%dn", *ptr); //10

返回0;

}

6.2 常量指针(Pointer to Constants)

常量指针(Pointer to Constants) :本质上是一个指针,但它指向的值是一个常量(只可读,不可修改)。由于它指向只可读不修改之间的值,所以指针不能通过它存储的地址间接修改这个地址的值,但是这个指针可以指向其他变量。

常量指针的声明格式如下:

const* 例如: const int* ptr;还有一个程序:

#includeint main(无效)

{

int var1=0;

常量int* ptr=var1;

*ptr=1;

printf("%dn", *ptr);

返回0;

我们来分析一下这个程序:

我们定义了一个变量var1并将其初始化为0。然后我们定义了一个指针常量ptr并将其指向var1。然后,我们尝试通过指针ptr来改变var1的值。最后我们打印出了ptr指向的地址内容。我们来编译:

main.c: 在函数“main”: 中

main.c:7:10: error: 只读位置“*ptr”的分配

*ptr=1;

^编译错误也很明显。 *ptr 是只读的。因此,var1的值不能通过ptr修改。

但是,我们可以将ptr指向其他变量:

#includeint main(无效)

{

int var1=0;

常量int* ptr=var1;

printf("%dn", *ptr); //0

int var2=20;

ptr=var2; //好的

printf("%dn", *ptr); //20

返回0;

}

6.3 指向常量的常量指针

如果你了解了上面两种类型,就很容易理解这个了。指向常量的常量指针意味着这个指针既不能指向其他地址,也不能通过该地址修改内容。

其声明格式如下:

const* const,例如: const int* const ptr;同样,在下面的程序中,我想你一定知道编译错误在哪里了。

#includeint main(无效)

{

int var1=0,var2=0;

常量int* 常量ptr=var1;

*ptr=1;

ptr=var2;

printf("%dn", *ptr);

返回0;

}编译结果:

main.c: 在函数“main”: 中

main.c:7:10: error: 只读位置“*ptr”的分配

*ptr=1;

^

main.c:8:9: error: 只读变量“ptr”的赋值

ptr=var2;

^

7 指针与函数

7.1 函数指针

指针和函数结合有两种情况: 指针函数和函数指针。

指针函数本质上是一个函数,它的返回值是一个指针。

int * func(int x, int y);参考:https://blog.csdn.net/qq_33757398/article/details/81265884

函数名本身就是一个指针(地址),这个地址就是函数的入口地址。

#includeint sum(int a, int b)

{

返回a+b;

}

int main()

{

printf("%pn", 总和);

返回0;

}输出:

0000000000401550

函数指针本质上是一个指针。只是它存储的地址恰好是一个函数的地址。

函数指针变量定义的格式一般为:

返回值(*变量名)(参数列表)如:

#includeint sum(int a, int b)

{

返回a+b;

}

int main()

{

printf("%pn", 总和);

int (*psum)(int, int); //函数指针变量,参数名可以省略

psum=总和;

printf("%pn", psum);

返回0;

}输出:

0000000000401550

可以查到0000000000401550,两个地址相等。

函数指针类型的定义:

typedef 返回值(*类型名)(参数列表);比如:

typedef int(*PSUM)(int, int);

PSUM pSum2=总和;

PSUM pSum3=总和;这样做的好处是,首先通过typedef 定义一个函数指针类型PSUM。定义之后,PSUM就相当于一个新的类型。您可以使用此类型来定义其他函数指针变量,而不必每次都重新定义它。使用int(*pSum)(int, int);定义函数指针变量。

#includeint sum(int a, int b)

{

返回a+b;

}

int func2(int a, int b)

{

返回a - b;

}

typedef int (*PFUNC) (int, int);

int main()

{

int (*psum)(int, int);

psum=总和;

printf("psum(4, 5):%dn", psum(4, 5));

PFUNC p2=func2;

printf("p2(5, 2):%dn", p2(5, 2));

p2=总和;

printf("p2(5, 2):%dn", p2(5, 2));

返回0;

}输出:

psum(4, 5):9

p2(5, 2):3

p2(5, 2):7

7.2 回调函数

参考:https://zhuanlan.zhihu.com/p/214157226

说到函数指针,还有一个不得不提的概念——回调函数。因为在实际项目代码中太常见了。

回调函数是通过函数指针调用的函数。如果你将一个函数指针(地址)作为参数传递给另一个函数,当这个指针用来调用它所指向的函数时,我们说它是回调函数。

那么为什么要使用回调函数呢?或者说使用回调函数有什么好处?回调函数允许用户将要调用的方法的指针作为参数传递给函数,这样函数在处理类似的事情时可以灵活地使用不同的方法。

怎么使用回调函数:

#includeint Callback_1(int a) ///回调函数1

{

printf("您好,这里是Callback_1: a=%d n", a);

返回0;

}

int Callback_2(int b) ///回调函数2

{

printf("你好,这是Callback_2: b=%d n", b);

返回0;

}

int Callback_3(int c) ///回调函数3

{

printf("您好,这里是Callback_3: c=%d n", c);

返回0;

}

int Handle(int x, int (*Callback)(int)) //注意这里使用的函数指针定义

{

回调(x);

}

int main()

{

句柄(4,Callback_1);

句柄(5,Callback_2);

句柄(6,Callback_3);

返回0;

}如上面代码所示:可以看到Handle()函数中的参数是一个指针。当在main()函数中调用Handle()函数时,函数Callback_1()/Callback_2()/Callback_3(),此时的函数名就是对应函数的指针。换句话说,回调函数实际上是函数指针的一种用法。

8 二维指针

二维指针,或辅助指针。它是一个指向指针的指针。例如:

#includeint main()

{

整数a=10;

int *pa=a;

int **ppa=pa;

printf("%p, %p, %p, %p, %p", a, pa, *pa, ppa, *ppa);

r

eturn 0; }输出如下: 000000000000000A, 000000000061FE14, 000000000000000A, 000000000061FE08, 000000000061FE14从输出结果也可以看到,pa存的内容pa= 000000000000000A,刚好与a的地址相同。而ppa存的内容ppa= 000000000061FE14也刚好等于pa的地址。它们之间的内存关系可以用如下的图表示: 二维指针的内存关系.png说到二维指针,那二维数组就不得不提了。我之前写过一篇关于二维数组与二维指针的,大家可以参考这篇文章。

8.1 命令行参数

处理命令行参数是指向指针的指针的一个用武之地。 一般main函数具有两个形参。第一个通常称为argc,它表示命令行参数的数目。第2个通常称为argv,它指向一组参数值。由于参数的数目并没有内在的限制,所以argv指向这组参数值(从本质上来说是一个数组)的第一个元素。这些元素的每个都是指向一个参数文本的指针。如果程序需要访问命令行参数,main函数在声明时就要加上这些参数。 int main(int argc, char **argv)举例: #includeint main(int argc, char *argv[]) { printf("argc: %dn", argc); // 打印参数,直到遇到NULL指针。程序名被跳过 while (*++argv != NULL) { printf("%sn", *argv); } return 0; }在windows上执行: test2.exe hello world 输出: argc: 3 hello world注意,如果命令行中传递的一个参数包括空格,就需要用 ""将参数括起来,比如: .test2.exe "hello word" 则上面的代码将输出: argc: 2

文章到此结束,如果本次分享的深入浅出C语言指针技巧与总结和的问题解决了您的问题,那么我们由衷的感到高兴!

用户评论

我要变勇敢℅℅

C语言真考验脑力的编程语言!

    有17位网友表示赞同!

追忆思域。

指针的概念是理解C语言的关键吧!这篇文章应该能帮我捋一捋思路。

    有12位网友表示赞同!

千城暮雪

感觉指针总是让人头疼,希望能学到一些好用的技巧啊!

    有5位网友表示赞同!

青袂婉约

终于有时间好好学习一下了,感谢分享这篇总结文章!

    有14位网友表示赞同!

站上冰箱当高冷

刚入门C语言,指针感觉有点抽象,希望这篇文章能给我解释清楚。

    有6位网友表示赞同!

请在乎我1秒

学习指针真的需要细心琢磨,这篇文章看起来很系统化!

    有12位网友表示赞同!

命该如此

看了题目就知道是重点内容了,C语言真是一门有深度啊!

    有16位网友表示赞同!

蔚蓝的天空〃没有我的翅膀

指针应用场景挺广泛的,总结文章应该能让我更好地理解它的作用。

    有15位网友表示赞同!

◆残留德花瓣

学习C语言,还是要多刷题巩固一下!这篇文章可以作为参考。

    有17位网友表示赞同!

何年何念

希望这篇文章能解释清楚指针的基本概念和常见操作啊!

    有8位网友表示赞同!

执拗旧人

C语言越来越常用,学会指针也是必不可少的技能了。

    有20位网友表示赞同!

病房

写过一些简单的C程序,感觉对指针还不够了解,这篇总结就比较实用啦。

    有19位网友表示赞同!

作业是老师的私生子

打算深入学习下C语言,指针知识估计很重要吧!这篇文章正好可以一睹为快。

    有9位网友表示赞同!

苏樱凉

每次学到指针都觉得脑子有点痛,希望能用通俗易懂的方式解释讲解!

    有14位网友表示赞同!

﹏櫻之舞﹏

文章标题看起来很全面,应该能把我对指针的所有疑问解答清楚了!

    有15位网友表示赞同!

箜篌引

喜欢C语言的简洁性和底层操作能力,指针就是其中很重要的组成部分!

    有19位网友表示赞同!

命里缺他

指针操作真的容易出错,这篇文章应该能提高我的编程安全性!

    有7位网友表示赞同!

回到你身边

想写成更高效的C代码,掌握指针技巧似乎很重要啊!

    有14位网友表示赞同!

夏日倾情

看了标题立马想到自己遇到的指针问题,希望能在这篇文章里找到答案!

    有15位网友表示赞同!

【深入浅出C语言指针技巧与总结】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:探索2020:我的个人成长蓝图揭秘 下一篇:揭秘卓越背后的秘密:探寻成功的真正动力