zoukankan      html  css  js  c++  java
  • C语言笔记(三):深入浅出常量、变量

    作者:老余

    博客地址www.yuxiaoshao.cn

    联系方式qq:1316677086

    有问题或者有错误请下方直接评论

    常量

    如果看官是在这个博客里来查资料的,那就不要来了,这里是放的我对本章内容的理解,以及扩展理解

    在写这个博客的时候,我觉得我有必要把有些闲话也记录下来, 各位看官可以把这个博客当做小说来看,当然是没有剧情的。想要剧情,做梦吧但是你好好看,我觉得还是能看懂吧,虽然可能也许有很多错误观点,也希望各位看官给我指正一下。我也是个小菜鸡

    何为常量?

    即不能被修改的变量,当然这里是在内存空间里开辟了一处空间,放在文字常量区

    为了更加深入的理解常量和变量,我这里把他们存储以及执行的过程都说一说

    在C中,内存可以分为堆、栈、静态区、文字常量、代码;这里的内存就理解为运行内存吧。

    • 堆:放函数参数、局部变量
    • 栈:存储动态生成的对象,一般由程序员分配释放。
    • 静态区或者全局区: 存储知全局变量道和静态变量。
    • 文字常量区 :存储常量字符串。
    • 代码区 : 存储函数体的2进制代码

    常量的分类

    整数常量

    常见的整数类型 int short long char(特殊的整数类型)

    又能用多种进制表达整数:例如2进制、8进制、16进制、10进制

    既然提到了进制,那我们来学习一下进制

    简单的进制分类

    2进制:

    • 为啥说是2进制呢?还不是因为只有2个数字来表达的一种方式,这两个数字就是0,1;你觉得怎么好好理解就怎么来就是咯
    • 为啥是0和1?不是0和2;这里我是这样理解的,如果不正确请打死我 计算机基础里说过,计算机起源有哪些?当然是电子管、晶体管、集成电路、大规模集成电路;(后缀都有个计算机)所以我们个人计算机就是集成电脑计算机咯。大规模的集成电路计算机就理解成大型的服务器吧。那种藏在池子里那种,或者是那个超级电脑。当然啥是超级计算机?就是计算超大数字计算的很快很快那种,大的要很多个房间又特别吃点电的那种;好了, 我不装b了。为啥是0和1,这样子理解,计算机里都是从0开始计数的。0到1也就是2位数,所以说2进制只有0和1咯。如果是由0、1、2组成的当然是交3进制咯,因为有3个数嘛

    8进制:

    • ok,楼上介绍了啥是2进制,当然这里为啥要叫8进制呢,很明显是有8个数啊。是哪8个呢? 你从0开始自己数8个呗;当然是0~7咯。为了和10进制区分(因为10里面也有0到7嘛),所以用开头是0(zero)的来区分。至于为啥用0来区分?可能是方便吧,这里就不深究了

    10进制:

    • 这个就不详细解释咯。当然综上所述当然是由0到9来表示咯。为了区分,就没有给他设置前缀咯

    16进制:

    • 综上所述。当然是0到15来表示咯,你想多咯,如果是这样怎么区分呢?所有很腻害的来了,前面的10个数还是用10进制的方式来表达,后面的6个数呢?当然用abcdef啊。为啥是6个字母。因为没有10,为啥没有10?因为2进制的时候就用了10了啊,而且很容易和10进制起冲突
    • 所以呢前面的10个数依旧是0~9,后面的6个数,a~f。为了区分,给了她一个前缀0x(ling埃克斯)

    进制的换算

    这里我说都是正数的操作

    刚才我扯到了进制的分类,当然如果考试的时候,代码的时候要换算进制咋办呢?我想让机器认识我表达 的数字咋办?

    所以呢,学习换算啊

    十进制转二进制

    这里我们先了解一下换算的姿势吧,先了解一下10进制翻译成2进制吧,毕竟这个经常考试

    好的,先理解一下,计算机内存里的bit和bite的关系,当然后者比前者多了一个e而已嘛,读音都是一样滴,但是习惯吧bite叫做大B,bit叫做小b。OK

    这里规定了一个单位法则一个B=8b,也就是说1字节=8位.为啥是8位,可能是我们喜欢8这个数字吧。总之就是这样了

    所以呢?这个和换算有毛的关系啊?

    当然我们一般都是用字节来存储的,所以呢?比如就是c语言的编辑器,我先弄一个字节的空间来表示一个数这里我就用表达一个十进制的14吧。透哦。这里提到了字节,为啥用一个字节来表示一个数字,这时候,又要知道为啥?因为在c里一个数字、字母、标点符号各占一字节,一个汉字占两个字节。为啥是这样,你就不要想太多了。好了,回到正轨。我现在是一个计算机,我想要理解用户给我传来的一个10进制的数:14;当然我是不能直接理解的。我得将14翻译成2进制的才可以,不然老纸也不知道啥意思,就像你给我说英语,我不翻译我啷个知道。当然我是不会主动学习英语的。好了。开始翻译,

    翻译方式一:

    首先将14除2,余数是啥?当然是0然后呢?剩下的7接着除2余数是1,剩下的3接着除以2,然后余数是1,然后剩下一个1,1除以2余数就直接是1咯,因为没得余数了。后了。所以我们得到的余数有0111,当然咯我们需要将和这个数字倒过来,即1110,但是我刚才bb过了。一个字节是用8位来表示的,所以得凑齐8个数字,才能召唤神龙啊,所以是00001110。当然如果你答题回答是1110我觉得应该不会错,毕竟数学逻辑上来说这个没毛病,有了解的看官可以回答一下。这个翻译的 方式我不知道你看懂没有,反正就是疯狂的除2,直到最后一位,除不动了。然后就直接把这个作为余数,然后吧结果连起来,在倒过来凑8位就ojbk。个人不是很喜欢这种方式,但是这种方式应该是初高中就说过了吧

    翻译方式二:

    看了楼上的方式一,真tm的烦,好多步骤,所以呢?这里在来个方式,还是用十进制的14为例,将14转成2进制的数字

    OK,首先我们先在小本本上列出0,2,4,8,16,32,64,128.256,512,1024,好了,为啥要这样做,你就不要管咯,干就完了,反正看到有1024就知道啥意思了吧,就是字节的大小。然后呢?为啥要写这些东西,当然是有道理的 ,0字节翻译二进制是00000000,2字节翻译二进制是0000010,接下来没有3字节,你要说3字节也可以,也就是说00000011;当然这里为了好看,就直接上的4字节翻译成00000100。好了,不扯了,不然字数太多了。欧克,发现没得,如果是10进制的奇数转换成二进制,最右边是1,如果是偶数,则是0;好了,这个是题外话,当然,不然你问我15怎么算,我就懒得回答你,因为奇数我建议你用方式一。

    好的知道了为啥小本本上要写这么多,你管三七二十一,你直接写了再说,反正写到512左右就OK,因为考试不会考多大的数字。好了,我们要算14,这里多少能组成14啊,当然是2+4+8咯当然你要加个0说0+2+4+8我就无话可说咯,这里是用能凑成的最简单的几个来凑。你也不要说用16-2,这里没有这种算法,你就直接用最简单的方式在这里面加成14就OK,好了我们知道了是2、4、8、所以呢?我们在2和4和8的下面写上1啊。其他没用到的就是0,所以就是0111后面的32和64等等下面也是写0,因为没用到嘛。但是这里超过了8个数字,那你有8个数字了后面的就不要了呗,所以就是01110000,当然这里也需要倒过来才是正确答案即00001110.为啥子要倒过来。你想想,2进制最小在右边,而10机制最小在左边,然后呢?你说该不该换

    综上所述,如果10进制转换成二进制,如果十进制是偶数,用方法二,如果是奇数则用方法一

    二进制转十进制

    这个最简单吧,既然和2有关,那么,直接把2作为底数,然后从右边开始,第1位是2的0次方,第二位是2 的1次方,第三位是2的2次方然后加起来就OK,当然这里针对于是1的情况,也不知道你们懂不懂。可能是有点难受,但是这个也是高中说过的。例如00001111转成十进制就是1x2的0次方+1x2的1次方+1x2的2次方+1x2的3次方=1+2+4+8=15

    如果给个111当然自己要知道前面是有5个0的即00000111;然后同理可得1+2+4=7

    十进制转八进制

    这里就只有一个方法

    这里我发现我一个错误,上文写八进制写的是8进制,以及16进制,这样的写法是不对的,但是我懒得改了。就这样吧好的。好的 之前十进制转二进制的时候是怎么做的?忘了。。。好吧,是直接除2,那么,十进制翻译成八进制呢?当然是除8啊。

    这里我用14为例。首先14除8余6,然后1除8就直接余1咯,所以呢结果是61么?当然要倒过来是16,但是16又像一个十进制,所以加上一个前缀0,则016;考试时候记得加上,不然就和十进制没差别了。

    八进制转十进制

    二进制转十进制是咋来着,从右边到左边一次2的啥次方加上就是咯。当然咯。八进制也是咯。例如我们的016转十进制。这里6x8的0次方+1x8的1次方=6+8=14;这里的0前缀就不要看啦,它就是个前缀。

    八进制转二进制

    这个时候杠精小伙伴来了。这个还不简单?先转十进制在转二进制啊。例如016,先转十进制是14在转二进制0001110

    也有直接转的。但是我还是算了吧,懒得说这个了。我也不会等我会了我就给你们说

    十进制转十六进制

    这里我纠正一下,之前我说的abcdf用的小写,这里不规范,需要用大写

    十进制转八进制是怎么来着?好像是除以8,那转十六进制就是除以十六吗?没错但是这里是大于等于16的数就可以在这样,例如14转换十六进制直接是E,为啥0到9代表的是0到9的十进制的数,如果超过了则是ABCDEF,这里的14,则是E啊。直接写E当然记得前缀0x则0xE,如果是大于等于16的十进制呢?比如18,则18/16余2,然后1除以16就直接用1作为余数咯,所以结果是21,但是倒过来就是12所以就是x012

    十六进制转十进制

    例如我这里有个十六进制的数0x1245需要转换成十进制,所以呢?

    从右到左5x16的0次方+4x16的1次方+2x16的2次方+1x16的3次方=5+64+512+4096=4677

    如果是0xF4呢

    则是4x16的0次方+Fx16的1次方,这里的F则是15因为是0到15嘛,用的英文字母表示的而已啦

    所以是4+240=244

    说了这么多,其实他们都是能互相转换滴,这里就不详细解释了。

    负数进制的换算

    这里需要用到补码以及反码的知识

    在计算机里,有机器数都是通过补码存储的

    • 负数以原码(是负数的原码)的补码形式表达==负数在内存中的存储是“取反加一”,
    • 补码:反码+1

    反码加一叫补码。

    负数十进制转二进制

    例如我们的十进制数-22怎么转换成二进制呢?

    首先我们直接先找到22的二进制数,即用22疯狂除2结果是01101倒过来就是00010110,在每次我们求二进制的时候,老余建议各位还是反过来验算一下。这里我们已经得到了22的二进制数(这里的二进制数是22的原码),

    由于计算机里,左边的一位也就是最高次位是表示的正负号也就是0正1符。这里我们直接得到-22的原码,即10010110。(因为22的原码0,0010110这里的0就是一个符号位,直接变成1就得到了1,0010110即-22的原码)但是计算机不理解,所及还需要获得-22的反码;

    现在我们需要用到反码(就是把0换成1,把1换成0)的知识咯。之前求到的1,0010110反过来就是11101001;这里就是得到的反码,

    然后在加1获取补码:所以-22二进制表达式就是11101010(这个补码才是计算机存储负数的方式)

    二进制转换成十进制(负数)

    这里还是用之前的例子为例,我们有个二进制数11101010

    先减一获得反码

    就得到11101001

    然后将反码转换成原码就是将0和1兑换回来得到:00010110

    在将原码转换成十进制:0x2的0次方+1x2的1次方+2的2次方+2的4次方=2+4+16=22但是这里是负数的二进制转换而来,所以是-22

    好滴终于把进制拉扯完了。接着回到整数常量去。

    定义一个整数常量

    首先呢,标识符就不多说了吧,但是有一个注意点的是,常量最好还是大写,因为这样和变量区分开。可以使用各种进制的方式来表达你想要表达的数字,一般我们都是用的十进制来表达的。但是这里有个考点,也就是对进制的考点,比如叫你判断以下常量是否合法:

    212         /* 合法的 */
    215u        /* 合法的 */
    0xFeeL      /* 合法的 */
    078         /* 非法的:8 不是八进制的数字 */
    032UU       /* 非法的:不能重复后缀 */
    

    之前说到了前缀,这里也顺带一下后缀吧,在c里面也可以说计算机里默认的浮点型是double默认的整型是int,但是如果我们需要表示长整型的时候呢?就需要定义一个long型的整数,OK,但是int型的长度是多少来着?的2的32次方除2减1;-2147483648~2147483647;因为默认的是带有符号的数嘛。但是long型肯定是比int型长的。所以如果我们输入一个>2147483647‬的数,例如121474836478,这里编译器就会乱来了。因为超出了int范围,而且也不知道是long型。所以会有乱码。所以我们给他添加一个后缀l你也可以大写L结果就是这样long a = 21474836478L ;这个时候它就认识咯。

    还有一个后缀叫U。这个我没怎接触,他就是用来表示无符号的意思,默认是定义的带符号的。例如int a = 100;但是我们想要一个不带符号的则给他添加一个后缀U 即int a = 100U;和unsigned int a = 100;

    还有一个叫f。这里顺带就说了吧,一般我们定义的浮点型默认的是double。至于为啥,可能是因为double是双精度的精确度更高,然后方便计算;所以呢?我们一般定义float浮点数的时候后面最好天机一个f后缀,这里我一般用小写。也就是说float f1= 10.f;

    定义常量的第一种姿势

    第一种我们用#define 预处理器(宏定义)的方式来定义,这里直接上代码

    这里是是通过c的编译机制来定义的一个TEXT的常量,当然这个TEXT不是类型,而是常量名。后面的01则是该常量的值,这里会默认根据你的常量值自动识别类型,当然咯,这里就是int型咯。如果你要定义一个字符串?直接#define str "张三"

    这里不添加分号

    至于为啥?因为宏定义的方式是采用的替换的方式,如果进行运算,或者打印输出的时候会将后面的 分号也一并替换过去

    #define TEXT 01
    

    定义常量的第二种姿势

    使用 const 关键字,使用该关键字来修饰你要定义的常量

    例如如下代码

    #include <stdio.h>
     
    int main()
    {
       const int  LENGTH = 10;
       const int  WIDTH  = 5;
       const char NEWLINE = '
    ';
       int area;  
      
       area = LENGTH * WIDTH;
       printf("value of area : %d", area);
       printf("%c", NEWLINE);
     
       return 0;
    }
    

    既然这里提到了#define以及const。那么我就简单的说说两者的区别

    #define和const的区别

    如果你没有耐心,关注前三个区别就可以了。其他的就当作是扩展把

    1. 编译器的处理方式不同

      • define是在预处理阶段处理(定义在main方法之外)
      • const是在编译运行的阶段处理(在函数里使用)
    2. 类型的安全检查不同

      • define没有类型,不做任何类型检查,仅仅是展开(这里的展开就是替换,也就是说,在后面代码有用到该常量的时候,就将该常量的值替换成你定义的常量)

      • const有具体的类型检查,因为在编译阶段会执行类型检查
    3. 存储的方式不同

      • define存储的时候,不会给该变量开辟内存空间,但是在使用该变量的时候,会开辟内存空间

      • const会给该常量开辟内存空间,因为本质还是变量,只不过是把变量的权限改为了只读(不可编辑)而已。这个可空间一般是静态区
    4. const更节约内存空间

      也就是说,如果使用#define定义一个变量。在后面使用该变量的时候,都会从新开辟一个内存空间来存放该变量

      而使用const定义一个变量,只有第一次在使用的时候,才会开辟内存空间,而且该常量名称是存储的是该内存空间的地址,也就是说,后面再此使用到该常量的时候,是用的同一个常量。

    5. const提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

    6. 宏替换只作替换,不做计算,不做表达式求解;

      • 这里的不做计算,是不会进行四则运算,各种运算都不会进行运算的。只会无脑的替换使用该常量的位置而已
    7. 宏预编译时就替换了,程序运行时,并不分配内存。

    tmd,写了5k多个字,才归纳了部分常量的知识,还是有点心累

    变量

    欧克,终于来到了变量 了。之前学习这个和常量就是一个相对比的知识点把。但是也不全是。如果不知道基本数据类型的就点击这里我这里就不再详细谈了。

    声明并定义一个局部变量

    如题所示。我们要定义一个局部变量首先需要明确变量的数据类型,这里我以整型为例。

    记住,这里是局部变量,是在花括号里面滴,也就是有存活范围的该范围就是花括号的范围

    int	 main(){
        int a;//声明并定义一个变量a
        printf("%d
    ",a);
        return 0;
    }
    

    这里声明并定义了一个变量a。a是变量名,int是变量类型;当然如果这样没有给a赋初值,c会基于a一个随机值作为他的初始值,但是考试的时候呢?就说这个没有初始值吧。因为也确实没有初始值,而是c给予该变量的一个随机值;也就说,局部变量没有赋初值,是乱码;(这是没有static修饰的前提下默认是auto型。如果有static修饰,则该局部变量的初始值为0);括号里的可以不用看,这里主要是学习局部变量的知识;

    在这里,c编译运行的时候,就会在中开辟一块空间,这个堆是属于ROM的,也就是运行内存里的,当然这里没有给a赋初值,所以c会给他一个随机乱码地址丢给他。这个空间是多大呢?int类型是占多大的空间就是多大的空间,回忆一下吧,int占用4个字节,short占用2个字节,long占用4个字节,char占用1个字节,double占用8个字节,float占用4个字节longdouble占用16个字节;一个字节也就是8位。所以int占用了32位(4x8)所以呢?默认是的带符号(singned)类型也就是也就是-(2的32次方除2)~~(2的32次方除2减1)

    好滴,说到了没有赋初值的情况了,现在谈一下赋初值的情况吧,直接上代码

    int	 main(){
        int b=666;//定义并赋初值一个变量b
        printf("%d
    ",b);
        return 0;
    }
    

    这里b,很明显我们用到了一个赋值运算符”=“,给b赋予了一个初始值666,这个666是在int范围之内的呢,在这里直接就赋初值给了b一个值是666,所以我们叫做定义且赋初值。从内存分配的角度来看,在rom内存的堆内存中开辟了一块变量区域,默认值是一串乱码,但是由于赋予了初始值,则将初始值放在了该地址处,且b就是指向该初始值的地址。我们可以用以下代码测试一下

    //打印a的地址以及a的初始值
    printf("a地址:%p
    ",a);
    printf("a初始值乱码地址:%d
    ",a);
    //打印b的地址以及b的初始值
    printf("b地址:%p
    ",b);
    printf("b初始值乱码地址:%d
    ",b);
    

    结果如图所示

    image-20200526085920630

    声明并定义一个全局变量

    这里说一说全局变量。当然全局变量意味着所有函数都能使用该变量,是定义在函数之外的。定义方式如下

    int e ;
    
    int main(){
    
    printf("e的初始值:%d
    ",e);
    
    return 0;
    
    }
    

    通过以上代码 。可以知道,全局变量是带有初始值的。且该初始值就是数据类型的初始值。整型默认是0;浮点型默认是0.000000,字符型默认是空格

    为啥全局变量会有一个初始值呢?

    因为全局变量默认是static类型数据。也就是是在main函数执行之前执行,和java里的static代码块相似。在执行.c文件的时候,至于什么是static。后面再说,这里只做有关变量的内容,简单的说一下,static是c中的一种存储类型。当然,你只要知道,全局变量是有初始值的,默认就是该数据类型的初始值,也就是上文说到的

    变量的声明

    变量的声明有两种方式,一种就是我们常用的直接声明并定义一个变量,不赋初值也就是一般我们所说的int a;

    虽然作为局部变量的时候c会给a一个随机的值,但是默认是没有初始值的,也就说这样只是声明一个变量,且数据类型是int

    还有一种方式使用extern关键字,这个extern的意思是外部的意思。后面将存储类的时候详细解释,既然这里用到了,还是简单的介绍一下,extern,修饰一个变量,意思是说,该变量只是声明,不在内存中开辟空间,当然,这里的声明是没有赋初值的,只要你没用到赋值运算符“=”name,且不是全局变量(外部变量)它就是没有初始值。这里需要注意,如果使用extern,必须在外部给该变量赋初值。因为这里的就是针对外部最近的一次该变量出现的地方,引用外部的一个声明。可能看到这里你还是一脸懵逼,没关系,直接上代码

    #include <stdio.h>
    int a ;//这个a是相对于main函数里的a是外部的变量,由于是局部变量,是有初始值的;该初始值是0;所以这里只有定义的作用,声明在main函数里执行到exter int a的时候就声明了;如果没有main函数里的extern int a;这个语句,这里的全局变量是声明并定义
    int main(){
    
        extern  int a ;//这里声明了一个变量a,没用用到=,则没有给a赋初值;而且使用到了extern关键字,就必须在外部有出现的a。才能使用,否则会报错。当然这里的外部,也就是局部变量或者是其他函数里,由于c是从上到下的顺序执行的,所以如果是函数里的a,则必须先调用函数,才能使用extern找得到a的地址;
    printf("a的初始值:%d
    ",a);
    return 0;
    }
    

    常量以及变量就扯到这里吧,这里需要注意在vc6.0里,声明定义变量必须定义在函数开头,其他编辑器我不知道,因为不习惯

  • 相关阅读:
    linux常用命令
    BAT:如何用批处理清空某个文件的内容
    Java_Blog01:编程入门
    Azkaban的job从创建到执行
    Sqoop1的导入导出
    Java 知识点干货
    启动Eclipse时报错如何解决?
    如何搭建JDK环境和配置JDK环境变量
    Create OpenStack and Docker base image based on CentOS7-mini ISO
    docker-ce install on CentOS7-mini
  • 原文地址:https://www.cnblogs.com/yuxiangqiezi/p/12963914.html
Copyright © 2011-2022 走看看