• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C语言 > C语言指针入门学习面面观

C语言指针入门学习面面观

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-28

通过本文主要向大家介绍了C语言指针入门学习面面观等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

这似乎是一个很凝重的话题,但是它真的很有趣。

1. 指针是指向某一类型的东西,任何一个整体,只要能称为整体就能拥有它自己的独一无二的指针类型,所以指针的类型其实是近似无穷无尽的

2. 函数名在表达式中总是以函数指针的身份呈现,除了取地址运算符以及sizeof

3. C语言最晦涩难明的就是它复杂的声明: void (*signal(int sig, void (*func)(int)))(int),试试着把它改写成容易理解的形式

4. 对于指针,尽最大的限度使用const保护它,无论是传递给函数,还是自己使用

先来看看一个特殊的指针,姑且称它为指针,因为它依赖于环境: NULL,是一个神奇的东西。先附上定义,在编译器中会有两种NULL(每种环境都有唯一确定的NULL):

#define NULL 0
#define NULL ((void*)0)
</div>

有什么区别吗?看起来没什么区别都是0,只不过一个是常量,一个是地址为0的指针。

当它们都作为指针的值时并不会报错或者警告,即编译器或者说C标准认为这是合法的:

int* temp_int_1 = 0; //无警告
int* temp_int_2 = (void*)0; //无警告
int* temp_int_3 = 10; //出现警告
</div>

为什么?为什么0可以赋值给指针,但是10却不行?他们都是常量。

因为C语言规定当处理上下文的编译器发现常量0出现在指针赋值的语句中,它就作为指针使用,似乎很扯淡,可是却是如此。

回到最开始,对于NULL的两种情况,会有什么区别?拿字符串来说,实际上我是将字符数组看作是C风格字符串。

在C语言中,字符数组是用来存储一连串有意义的字符,默认在这些字符的结尾添加'\0',好这里又出现了一个0值。

对于某些人,在使用字符数组的时候总是分不清楚NULL与'\0'的区别而误用,在字符数组的末尾使用NULL是绝对错误的!虽然它们的本质都是常量0,但由于位置不同所以含义也不同。

开胃菜已过

对于一个函数,我们进行参数传递,参数有两种形式: 形参与实参

int function(int value)
{
    /*...*/
}
//...
function(11);
</div>

其中,value是形参,11是实参,我们知道场面上,C语言拥有两种传递方式:按值传递和按址传递,但是你是否有认真研究过?这里给出一个实质,其实C语言只有按值传递,所谓按址传递只不过是按值传递的一种假象。至于原因稍微一想便能明白。

对于形参和实参而言两个关系紧密,可以这么理解总是实参将自己的一份拷贝传递给形参,这样形参便能安全的使用实参的值,但也带给我们一些麻烦,最经典的交换两数

void swap_v1(int* val_1, int* val_2)
{
  int temp = *val_1;
  *val_1 = *val_2;
  *val_2 = *val_1;
}
</div>

这就是所谓的按址传递,实际上只是将外部指针(实参)的值做一个拷贝,传递给形参val_1与val_2,实际上我们使用:

#define SWAP_V2(a, b) (a += b, b = a - b, a -= b)
#define SWAP_V3(x, y) {x ^= y; y ^= x; x ^= y}
</div>

试一试是不是很神奇,而且省去了函数调用的时间,空间开销。上述两种写法的原理实质是一样的。

但是,动动脑筋想一想,这种写法真的没有瑕疵吗?如果输入的两个参数本就指向同一块内存,会发生什么?

...
int test_1 = 10, test_2 = 100;
SWAP_V2(test_1, test_2);          
printf("Now the test_1 is %d, test_2 is %d\n", test_1, test_2);
.../*恢复原值*/
SWAP_V2(test_1, test_1);
printf("Now the test_1 is %d\n", test_1);  
</div>

会输出什么?:

$: Now the test_1 is 100, test_2 is 10
$: Now the test_1 is 0
</div>

对,输出了0,为什么?稍微动动脑筋就能相通,那么对于后面的SWAP_V3亦是如此,所以在斟酌之下,解决方案应该尽可能短小精悍:

static inline void swap_final(int* val_1, int* val_2)
{
  if(val_1 == val_2)
    return;
  *val_1 ^= *val_2;
  *val_2 ^= *val_1;
  *val_1 ^= *val_2;
}
#define SWAP(x, y) \
do{         \
  if(&x == &y)  \
    break;   \
  x ^= y;   \
  y ^= x;   \
  x ^= y;   \
}while(0)
</div>

这便是目前能找到最好的交换函数,我们在此基础上可以考虑的更深远一些,如何让这个交换函数更加通用?即适用范围更大?暂不考虑浮点类型。 提示:可用void*

与上面的情况类似,偶尔的不经意就会造成严重的后果:

int combine_1(int* dest, int* add)
{
  *dest += *add;
  *dest += *add;
  return *dest;
}
int combine_2(int* dest, int* add)
{
  *dest = 2* (*add);//在不确定优先级时用括号是一个明智的选择
  return *dest;
}
</div>

上述两个函数的功能一样吗?恩看起来是一样的

int test_3 = 10, test_4 = 100;

combine_1(&test_3, &test_4);
printf("After combine_1, test_3 = %d\n",test_3);
.../*恢复原值*/
combine_2(&test_3, &test_4);
printf("After combine_2, test_3 = %d\n",test_3);

</div>

输出

$: After combine_1, test_3 = 210

$: After combine_2, test_3 = 210

</div>

如果传入两个同一对象呢?

... /*恢复test_3原值*/
combine_1(&test_3, &test_3);
printf("After second times combine_1, test_3 = %d\n",test_3);
...
combine_2(&test_3, &test_3);
printf("After second times combine_2, test_3 = %d\n",test_3);
</div>

输出

$: After second times combine_1, test_3 = 30

$: After second times combine_2, test_3 = 20

</div>

知道真相总是令人吃惊,指针也是那么令人又爱又恨。

C99 标准之后出现了一个新的关键字, restrict,被用于修饰指针,它并没有太多的显式作用,甚至加与不加,在 你自己 看来,效果毫无区别。但是反观标准库的代码中,许多地方都使用了该关键字,这是为何

  • 首先这个关键字是写给编译器看的
  • 其次这个关键字的作用在于辅助编译器更好的优化该程序
  • 最后,如果不熟悉,绝对不要乱用这个关键字。

关于数组的那些事

数组和指针一样吗?

不一样

要时刻记住,数组与指针是不同的东西。但是为什么下面代码是正确的?

int arr[10] = {10, 9, 8, 7};
int* parr = arr;
</div>

我们还是那句话,结合上下文,编译器推出 arr处于赋值操作符的右侧,默默的将他转换为对应类型的指针,而我们在使用arr时也总是将其当成是指向该数组内存块首位的指针。

//int function2(const int test_arr[10]
//int function2(const int test_arr[]) 考虑这三种写法是否一样
int function2(const int* test_arr)
{
  return sizeof(test_arr);
}
...
int size_out = sizeof(arr);
int size_in = function2(arr);

printf("size_out = %d, size_in = %d\n", size_out, size_in);

</div>

输出:

size_out = 40, size_in = 8
</div>

这就是为什么数组与指针不同的原因所在,在外部即定义数组的代码块中,编译器通过上下文发觉此处arr是一个数组,而arr代表的是一个指向10个int类型的数组的指针,只所谓最开始的代码是正确的,只是因为这种用法比较多,就成了标准的一部分。就像世上本没有路,走的多了就成了路。"正确"的该怎么写

int (*p)[10] = &arr;
</div>

此时p的类型就是一个指向含有10个元素的数组的指针,此时(*p)[0]产生的效果是arr[0],也就是parr[0],但是(*p)呢?这里不记录,结果是会溢出,为什么?

这就是数组与指针的区别与联系,但是既然我们可以使用像parr这样的指针,又为什么要写成int (*p)[10]这样丑陋不堪的模式呢?原因如下:

回到最开始说过的传递方式,按值传递在传递arr时只是纯粹的将其值进行传递,而丢失了上下文的它只是一个普通指针,只不过我们程序员知道它指向了一块有意义的内存的起始位置,我想要将数组的信息一起传递,除了额外增加一个参数用来记录数组的长度以外,也可以使用这个方法,传递一个指向数组的指针 这样我们就能只传递一个参数而保留所有信息。但这么做的也有限制:对于不同大小,或者不同存储类型的数组而言,它们的类型也有所不同

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

相关文章

  • 2017-05-28C语言中的strdup()函数和其与strcpy()函数的区别
  • 2022-04-30C语言究竟是一门怎样的语言?
  • 2017-05-28C语言实现大整数加减运算详解
  • 2017-05-28C++输入一个字符串,把其中的字符按照逆序输出的两种方法解析
  • 2017-05-28C 字符串数组排序的小例子
  • 2017-05-28SQL Server中的数据复制到的Access中的函数
  • 2017-05-28深入C中常用的三种排序方法总结以及探讨分析
  • 2017-05-28C语言高斯消元法的使用详解
  • 2017-05-28C语言 坐标移动详解及实例代码
  • 2017-05-28浅析C语言中的内存布局

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • 深入解析设计模式中的适配器模式在C++中的运用
    • 浅析C语言中assert的用法
    • VC小技巧汇总之控件技巧
    • C C++ 算法实例大全
    • C++中const应放在类型前还是后
    • 对C语言中递归算法的深入解析
    • C语言编程时常犯十八个错误小结
    • 详解C++编程中类的声明和对象成员的引用
    • C语言中函数参数的入栈顺序详解及实例
    • C++实现读取特定路径下文件夹及文件名的方法

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有