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进行比较,如果小于则相等;反之则不相等。)

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

C语言学习总结

本科一年级学习 C++,没有学习C。在印象中C是C++除去类后的版本。经过9年后,也该给自己做一个总结 在开发中,出现一些混淆,从前年开始决定认真学习下C ,主要看的书是C发明者Kernigan & ...
  • cmsbupt
  • cmsbupt
  • 2016年04月12日 17:29
  • 653

C语言学习总结(五)——C库函数总结

C 库函数主要指那些由美国国家标准协会(ANSI)或国际标准化组织(ISO)发布的标准中规定的库函数,按照标准 C 的要求来进行 C 语言编程是很重要的,因为这样你的代码才有可能跨平台使用。 最...
  • myintelex
  • myintelex
  • 2016年12月26日 18:22
  • 478

C语言程序设计学习总结

最近忙于学习C语言程序设计,考计二C。都搞得我头晕眼花,天天都是刷题。马上就要考计二C了。我想总结一下我对C语言的理解。 C不同于java的最大区别在于C是面向过程,而java是面向对象。刚开始我也搞...
  • qq_35542689
  • qq_35542689
  • 2017年03月23日 15:57
  • 1238

C语言心得体会

在科技快速发展的今天,计算机在人们生活中的作用越来越突出,而C语言作为一种计算机的语言,我们学习它有助于我们更好地了解计算机。通过学习,我们可以了解到计算机是如何执行程序命令的。不仅如此,我们还可以根...
  • u011555630
  • u011555630
  • 2015年12月21日 20:19
  • 2934

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

高级软件工程(C语言实践篇)学习心得 张文勇 + 原创作品转载请注明出处 + 《软件工程(C编码实践篇)》MOOC课程http://mooc.study.163.com/course/USTC-10...
  • wy_zhang666
  • wy_zhang666
  • 2016年11月09日 13:43
  • 434

c语言学习心得(四)循环结构

循环结构是程序中一种很重要的结构。其特点是,在给定条件成立时,反复执行某程序段,直到条件不成立为止。给定的条件称为循环条件,反复执行的程序段称为循环体。C语言提供了多种循环语句,可以组成各种不同形式的...
  • HyNeverGiveUp
  • HyNeverGiveUp
  • 2016年11月17日 22:28
  • 394

C语言 函数小总结

函数乃C语言精华之所在。对于函数我们需要知道: 1 为什么要用函数? 函数的好处大致可以分为3点:1提高代码的复用性2提高代码的维护性3可以实现迭代开发,也就是可扩展开发。 2 ...
  • Insanity666
  • Insanity666
  • 2016年07月21日 16:16
  • 155

L1-030. 一帮一

“一帮一学习小组”是中小学中常见的学习组织方式,老师把学习成绩靠前的学生跟学习成绩靠后的学生排在一组。本题就请你编写程序帮助老师自动完成这个分配工作,即在得到全班学生的排名后,在当前尚未分组的学生中,...
  • dengkuomin
  • dengkuomin
  • 2017年03月12日 10:29
  • 497

数据结构(c语言版)总结

1. 数据结构的4中基本类型 1、集合 2、线性结构 3、树形结构 4、图、网状结构 2. 结构定义中的关系描述是数据元素之间的逻辑关系,因此叫逻辑结构 3. 数据存储结构:顺序存储结构、链式...
  • Li__YingYing
  • Li__YingYing
  • 2013年06月13日 17:05
  • 1027

c语言课程总结

这个星期的c语言课程学习结束了,学习c是因为很多语言都具有c的影子,作为前端学习,虽然不是后端,但是对后端语言的学习也是必不可少的,而且前端学习涉及的东西太广泛,所以,拥有一个编程的思维,和一个良好的...
  • qq535972008
  • qq535972008
  • 2015年09月23日 14:35
  • 695
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C语言学习总结(一)
举报原因:
原因补充:

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