C语言学习总结(一)

原创 2016年06月01日 23:24:18

定义和声明的区别

这个通常是一个常见的但是又容易忽略的问题,需要好好正视一下到底什么才是变量的声明?什么才是变量的定义?以及它们彼此之间的区别?怎么分辨?
让我们先来看看一个例子:
uint i;
uint j = 0;
extern uint i;

在这个例子里面,哪个是定义?哪个是声明?定义又分什么情况?在内存中有什么表现?

首先理解下定义:定义就是由编译器负责创建一个对象并且为这个对象分配一块内存空间给它使用,而且还给这块内存空间起了一个名字,这个名字就是我们平时提及的变量名;这里的变量名是与分配到的内存空间联系并且唯一匹配的,而且这块空间一旦分配了就无法再改变。也就是说一个变量或者一个对象在一个代码块中(代码区域内有效)只能被定义一次,如果重复定义则编译器会警示。

而声明是指相当于告知编译器这个声明的变量名已经与一块内存空间互相匹配。但是这里要弄清楚,此时还不一定已经分配空间,只是相当于一个预先打招呼,通知编译器说这个变量名已经被提前预定使用了,并且要与一块内存空间互相匹配用的,其它地方不能使用这个预定的变量名了。
所以这个例子里面,很显然的,只有第二个是定义;其余都是声明;

至于如何判断呢,一般来说如果是extern的全局变量、未赋初值的变量都可以算是声明,而其余的形式则都基本是定义。当然这里要注意一点就是第一个,这个int i虽然严格意义上是一个声明,但是这个语句执行完后其实也就已经等同于定义了,也就是此时编译器已经为这个i分配了4Byte内存空间,但是这个空间内的值是不确定的而已。所以声明和定义也是有联系的,在这种情况下,声明也就等于完成了定义,只是还没初始化而已。
故相对来说,函数的声明和定义也可以用上述的方式加以区别,相比之函数的声明和定义就好分辨多了。

关于i++和++i

这是一个老话题了,这两个都是自加操作,但是区别在于i++是先使用这个值而后再自加,++i则是先自加再引用这个值;让我们来看看一个例子:

#include <stdio.h>
int main()
{
    int a,b,c;
    a = 1;
    b = a+++a;
    a = 1;
    c = ++a+a;
    printf("a=%d,b=%d,c=%d\n",a,b,c);
    return 0;
}

结果输出的是a=2,b=3,c=4
所以i++就是在该行语句尚未结束之前i的值尚未完成自加操作,要等到该行语句执行完后i的值才会执行自加;同理++i。

sizeof()

我想说的是这个玩意儿首先不是一个函数…以前刚学c的时候,真以为是一个函数调用,后来无奈地被人指着鼻子说哪个傻逼告诉你这是一个函数的囧~
言归正传,首先sizeof()是一个静态运算符,是负责判断数据类型长度符的关键字,静态的意思就是说在编译的过程中,这个运算符是不会执行括号内的表达式或者代码的,它只是负责由系统测出括号内那部分数据在内存中所占的字节大小,所以其返回的只是一个整型值,代表了多少个字节;简单的说其作用就是返回一个对象或者类型所占的内存字节数。
还是举个栗子:

int i = 1;
sizeof(i++);

在32位系统中这段代码执行完后sizeof返回的是4,这个可以理解,但是i呢?
当然了,i还是1,理由就像上面说的一样,这个过程里面的i++是不会被执行的,sizeof只负责检测出类型所占的内存字节大小,其余它都不会管。
于是乎,在这个sizeof下又有几个问题出现了,先看看第一个:
结构体对齐
直接看一个栗子:

typedef struct example
{
    int i;
    int size;
    char NAME[8+1+3+1];
    char j;

}test;

sizeof(test)后,在GCC中输出的结果是24;
这也是一个常见的问题,很多人都会有疑惑而且总是概念模糊,这里顺便提点下几个重点的有联系的概念;
首先看一看这个sizeof结果为什么会是24,这里先用我自己的方式理解一下过程:
首先观察一下这个结构体内成员类型,发现是int类型占的字节大小是最大的,所以对齐的最大尺度是4Byte;那么先对齐第一个i,刚好4;而后第二个size也是4;第三个是一个字符数组,里面有13个元素并且占用了13个字节,由于最大的对齐尺度是4,所以这个13Byte数组要先拿12个Byte也就是前面12个元素出来,刚好对齐3次;剩下的一个是NAME[12]这个元素,第四个是j占用空间是1字节,所以编译器此时会将这两个一起合并并且进行4字节对齐。所以综上所述,对齐后的sizeof是4+4+12+4=24;很多新手一开始的时候问的就是为什么不是22?因为这里不就是4+4+13+1=22么?所以这里就是涉及到这个对齐尺度的问题。我们理解一下为什么一定需要有一定的对齐尺度呢?
编译器之所以要对结构体成员进行对齐,很大原因是为了加快取值效率;这里就涉及到了字节对齐的概念,所谓字节对齐就是说为了加快数据和指令的存取效率,CPU一般会在内存中存放的数据进行对齐,这可以使得取数据和取指令的时候极大地提高效率。比如说一个32位的int变量需要取出存放到寄存器中,此时如果cpu是从内存偶地址的地方开始取这个变量数据的话,而且int变量也是从偶地址开始对齐存放,那么只需要用一个取值周期就可以拿到这个变量。但是如果此时int变量是从奇地址开始存放的,由于此时奇地址开始的32位地址就不可能够空间存放一个完整的int,因此需要cpu花费两个取值周期才能拿到这个数据。结构体对齐的根本原因也是在此;
形象点来理解,我们举一个栗子:
比如一个人在一个房间中,此时我们抽象一下,这个人的整个身体就是一个int类型的变量数据,而房间等同于编译器每一次要取得空间大小。如果是对齐取得这个人,那么也就是说这个人整个身体都是在这个房间中;如果是不对齐的情况下取得,那么此时这个人的身体就有一部分是不在这个房间内的比如说他有一只脚放在了门外,此时编译器要拿到整个人的身体就又要用一个房间大小的空间去拿欠缺的另一只脚。
所以,对齐的根本意义在于为了提高取值效率,那么最好的方法就是用结构体内最大成员类型的长度作为一个对齐的尺度,原则就是说,只要能够满足编译器每次都能用一次取值周期取到一个完整的值就行。
理解了这点,相信对齐的问题也就可以迎刃而解了。但是我们还是来看一个栗子,这个栗子是前阵子在讨论群上别人发问的:

struct test{
    unsigned x:30;
    unsigned y:1;
    unsigned z:32;
    }

sizeof(test)后结果是什么呢?

答案是8,;这里估计会有很多人有疑问,为何此处sizeof是8?不是应该就是12吗?刚好3个int长度不是么?

这里就涉及到一个位域的概念了,位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
通常这种位域的结构是用在了与寄存器相关的或者与特定结构体联系的地方,比如在Linux内核源码中就有很多这种位域的使用,比如像段描述符,段选择符,段表页表等都使用了这种定义方式,所以这种位域的结构是为了能够针对寄存器类型的结构对其中的某位进行特定的操作从而不影响其他位。
因此,这个栗子之所以等于8,是因为第一个x和第二个y加起来的长度只有31位也就是还不到4字节的长度,所以根据类型最大尺度对齐的原则,这里就可以合并这两个,所以整个sizeof的长度就是8.

总结(一)

int类型是一个比较特殊的东西,首先它可以用来表示当前机器的寄存器所能存取的位数,比如32位指的就是说寄存器中存的指令数据是32个bit的,这也就是说为什么在不同的编译器环境下(正确应该说不同cpu架构下)int变量所占的内存大小可能不一样的原因;而且也涉及到了一个指针长度的问题,这里也顺便说一下,通常来说,无论指向什么类型的指针(指针变量严格来说其实不完全等同于指针,这两个概念接下来会详细区别好),在32位机上面所有指针的长度都是4Byte大小而已,这个是绝对的。所以在32位机上面sizeof(int) == sizeof(char *p)是成立的。

补码的概念:补码是极其重要的一个计算机知识点,补码是为了在计算机中引入负数的概念,比如char变量是一个字节大小的,其所能表示的数值范围是介于0000 0000-1111 1111也就是十进制的0-255,但是为了能够表示负数,所以计算机中规定了以1开头的都是负数表示,所以char变量的1111 1111(对应十进制255)就是指-1这个负数,往下递减比如1111 1110(对应十进制254)就是指-2,以此类推一直到1000 0000(对应128)指的就是-128,然后从0000 0000开始代表为0,一直递增到0111 1111(对应127)指的就是127这个正数,所以对应后的结果就是char的范围从-128~127。其他的变量也是同样的原理。这里可以抽象成一个圆,在圆上某一点是0000 0000也就是0,从0往右走是0000 0001也就是1然后一直到0111 1111也就是127。从0往左走是1111 1111也就是-1,一直到1000 0000也就是-128。这样可以比较容易而且形象地理解好补码的意义。

浮点数:关于浮点数其实在计算机中是将数学上的小数概念抽象为浮点型数据的,但是在数学的概念中,数与数之间是连续的,也就是说任意两个数之间有着无限个数;而在计算机内部则不一样,它不可能可以提供这种无限连续性的小数值,所以浮点型在计算机中是离散的,数与数之间是有限个的。以至于说如果有时候数与数之间我们想要表达的数是浮点型无法表达的话,那么我们就会取最接近表达数的浮点型数据来表示。在c语言浮点型数据中,用inf表示无穷大,nan表示不存在的浮点数。也就是说可以用浮点数表示无穷大,但是整型则没有这种定义。
而有一个细节需要注意的是:关于浮点数的比较,在c中如果想对两个浮点型数据进行逻辑比较的话,比如判断是否相等 == ,那么一般是不可以用 == 直接比较的(有可能会失败出错),原因在于比如1.20和1.2在计算机中用浮点型表示在有些编译器中是完全不一样的。如果需要对浮点型数据进行比较那么应该用fabs(f1-f2) < le-12来进行判断(fabs()是一个计算浮点型数据相差值的专用函数,用这个返回的差值与e-12进行比较,如果小于则相等;反之则不相等。)

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

【IOS 开发学习总结-OC-7.4】objective-c与c语言的预处理

在编译器对 C或者 objective-c 进行编译前,编译器会对这些预处理命令进行处理,然后将这些预处理的结果与源程序一起编译。预处理命令的2个特征: 1.都必须以#开头 2.通...

初入IOS学习总结——C语言基础(一)

博主过于懒惰~从申请到现在已经过了1个多礼拜才开始记录自己的第一篇博文

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

C语言学习总结(四)——数据结构

程序 = 数据结构 + 算法。 当我们需要解决一个计算机问题,大致的步骤是这样的: 1. 从一个具体的问题抽象出一个适当的数学模型 2. 设计一个解决这个模型的算法 3. 编写相应的程序,测试...

C语言学习一些总结

惭愧,现在还在学C语言,都怪自己以前不学好。 1.一维数组定义与初始化(): int array[5];//声明一个数组, int array[5] = {1,2,3,4,5};//声明的时...

c语言的申明,指针和数组的学习总结

c语言的申明的总结: 这周复习了一下c语言,下面是一些学习心得 首先是申明: 比如:extern char *( *hander)(int a); 那么这个申明该怎么理解呢? A  申明按照...

JNA调用C语言动态链接库学习实践总结(指针模拟)

最新因为项目需要,学习了一下JNA框架,在这里记录一下学习和使用心得,给大家分享,希望能帮助新手。 本文主要讲解如何使用JNA框架轻松调用C语言动态链接库,如何使用JNA模拟C语言参数(例如数组、指...

JNA调用C语言动态链接库学习实践总结

非常感谢Linux社区的zht666给我指了一条明路,本文转载自: http://www.linuxidc.com/Linux/2014-04/99688p2.htm 2.JNA模拟普通传值参数...

软件工程线上课程(C语言实践篇)学习心得总结

王俊杰 + 原创作品转载请注明出处 + 《软件工程(C编码实践篇)》:MOOC课程http://mooc.study.163.com/course/USTC-1000002006一、 前言软件工程是一...

C语言再学习之进制转换总结

二进制数、八进制数、十六进制数与十进制数相互转换的方法:按权展开求和法 二进制与十进制间的相互转换: 二进制转十进制:按权展开求和 例如: 二进制101.01  转为十进制 1×2^2+0×...

阶段性学习总结--C语言入门

心情有点复杂,不知道从何说起!先说一下自己的ID吧,因为原来是做网络优化的,是通讯网络优化,和互联网没有一丁点关系,由于工作性质的原因(长期出差在外),我决定转行做一个码农,我相信自己能做好IT这个行...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)