朱有鹏嵌入式Linux C基础总结

本文总结于朱有鹏物联网大讲堂Linux C基础视频,希望各位批评指正!
感谢朱老师的帮助^_^

 1.C语言写代码步骤:
     第一步:编辑源代码(使用vi或者其他编辑器)
     第二步:编译。编译就是用编译器把源程序转化成可执行程序的过程,编译要用到编译器。
            我们在linux中使用编译器一般是gcc。
            譬如:gcc hello.c 把当前目录下hello.c文件编译,得到的可执行文件名字叫a.out
         也可以自己指定编译后生成的可执行程序的名字,使用gcc hello.c -o hello
  第三步:执行编译生成的可执行程序,执行方式是./hello
  第四步:调试。当你执行后发现程序结果不对,不是自己想要的,这时候就是返回来看源代码哪里不对。
          然后修改,再编译执行,再看结果。如此循环直接结果正确。

2.VMWare共享文件夹使用
     在linux中,直接到 /mnt/hgfs目录下,即可找到刚才第二步中Name相同的名字的文件夹,
     这个目录即是第一步中Windows中目录在linux下的映射。

3、C语言数据类型
     3.1、整形
         C语言中的整形对应数学中的整数,整形变量是用来描述一个整数值的,整形变量经过计算后也
         只能是整数(整型),不可能出现小数(浮点型).
     3.2、浮点型
         C语言中浮点型对应数学中的小数。浮点型有float和double两种。使用方式相同,不同在于
         表示范围和精度。float表示的范围小,精度低(小数点后6位);
         而double表示范围大,精度高。(小数点后16位)
         范围是说表示的数有多大,精度是指这个数的分辨率有多细
          注意:printf中打印float或double类型,都是用%f,不能用%d。
     3.3、字符型
         字符型对应ASCII字符。ASCII字符是一种编码,就是用数字编码来表示一个符号的一种方法
         本质上说,字符型其实也是整形,只是这些整形数被用来表示一些字符的ASCII编码值,
         所以叫做字符型。 字符型一般用8位二进制表示,无符号字符型范围是0~255.
         字符型其实是一种比short还短的整形,所以它可以和int相运算。
     3.4、有符号数和无符号数
         数学中数是有符号的,有整数和负数之分。所以计算机中的数据类型也有符号,
         分为有符号数和无符号数。
    
          有符号数:
              整形:signed int(简写为 int)
              signed long,也写作signed long int,(简写为long)
              signed short,也写作signed short int(简写为short)
              signed(表示signed int)
              浮点型:
              signed float(简写为float)
              signed double(简写为double)
              字符型:
              signed char(简写为char)
       
         无符号数:
              整形:整形无符号数,用来表示一些编码编号之类的东西。譬如身份证号,房间号
                 unsigned int(没有简写)
                 unsigned long int(简写unsigned long)
                 unsigned short int(简写为unsigned short)
          对于char short int long等整形类型的数,都分有符号有无符号数。
          而对于float和double这种浮点型数来说,只有有符号数,没有无符号数。
     对于C语言来说,数(也就是变量)是存储在内存中一个一个的格子中的。存储的时候是以二进制
     方式存储的。对于有符号数和无符号数来说,存储方式不同的。
         譬如对于int来说unsigned int 无符号数,32位(4字节)全部用来存数的内容所以表示的数的
         范围是0 ~ 4294967295(2^32 - 1)
         signed int有符号数,32位中最高位用来存符号(0表示正数,1表示负数),剩余的31位
         用来存数据。所以可以表示的数的范围-2147483648(2^32)  ~ 2147483647(2^31 - 1)
     结论:从绝对数值来说,无符号数所表示的范围要大一些。因为有符号数使用1个二进制位来表示
           正负号。
         浮点数:没有无符号浮点数。也就是说,小数一般只用在数学概念中,都是有符号的。
         字符型:字符型有无符号数
            unsigned char(没有简写)
        注意:对于整形和字符型来说,有符号数和无符号数表示的范围是不同的。
              譬如字符型,有符号数范围是-128~127,无符号数的范围是0~255。
              a++是先执行表达式后再自增,执行表达式时使用的是a的原值。
                               ++a是先自增再执行表达示,执行表达式时使用的是自增后的a。

4.程序结构
      4.1、switch case
          1、case中必须是常数,而且必须是整形(不能是float double,可以是int char)
          2、一般来说,每个case中代码段后都必须有一个break
          3、case之后一般都会有default。语法上允许没有default,但是建议写代码时一定要写。
     4.2、switch case和if else对比:
          1、if else适合对比条件比较复杂,但是分支比较少的情况;switch case适合那种对比条件
            不复杂,但是分支数很多的情况。
          2、所有的选择结构,其实都可以用if else来实现。但是只有部分才可以用switch case实现。
             一般的做法是:在适合使用switch case的情况下会优先使用switch case,如果不适合
             使用switch case,则不得不使用if else。

5.复合数据类型
     所谓复合数据类型,是指由简单数据类型,经过一定的数据结构封装,组成的新的数据类型。
     譬如数组、结构体。
     5.1、数组的初始化
          第一种:完全初始化。依次赋值
          第二种:不完全初始化。初始化式中的值从a[0]开始,依次向后赋值,不足的默认用0填充赋值
     5.2、数据类型实质
          C语言程序中,变量的实质就是内存中的一个格子。当我们定义(创造一个变量)了一个变量后,
          就相当于在内存中得到了一个格子, 这个格子的名字就是变量名,以后访问这个内存格子
          就使用该变量名就行了。这就是变量的本质。

          数据类型的实质是内存中格子的不同种类。譬如在32位机器上
          短整形格子(short)                占用2字节空间 16位
          整形格子(类型是int)、            占用4字节空间 32位
          单精度浮点型格子(float)、        占用4字节空间
          双精度浮点型格子(double)、       占用8字节空间 64位
          字符型格子(char)。              占用1字节空间 8位
     5.3、sizeof运算符
          作用:返回一个变量或者一个数据类型的内存占用长度,以字节为单位。
               sizeof(a) / sizeof(a[0])    测试一个数组中究竟有多少个元素

     基础知识:
     1、在C语言中引用一个单个字符时,应该用单引号''括起来,譬如'a'。
     2、定义数组同时初始化,则可以省略数组定义时[]中的长度。C语言编译器会自动推论其长度,
        推论依据是初始化式中初始化元素的个数。 由此可知,省略[]中数组元素个数只有一种情况,
        那就是后面的初始化式必须为完全初始化。
     3、在C语言中引用一个字符串时,应该用""括起来,譬如"abcde"
     "abcde"实际上有6个字符,分别是'a' 'b' 'c' 'd' 'e' '\0'
        '\0' 这个字符是ASCII码表中的第一个字符,它的编码值是0,对应的字符是空字符(不可见字符,
        在屏幕上看不见,没法显示,一般要用转义字符方式来显示 。譬如'\n'表示回车符,
        '\t'表示Tab,'\0'代表空字符); '\0'是C语言中定义的字符串的结尾标志。
        所以,当c语言程序中使用"abcde"这种方式去初始化时, 编译器会自动在字符'e'后面
        添加一个'\0'。于是乎变成了6个字符。

     5.4、指针与数组的初步结合
          数组名:做右值时,数组名表示数组的首元素首地址,因此可以直接赋值给指针。
          如果有 int a[5];  则 a和&a[0]都表示数组首元素a[0]的首地址。 而&a则表示数组的首地址。
          注意:数组首元素的首地址和数组的首地址是不同的。前者是数组元素的地址,而后者是数组
                整体的地址。两个东西的含义不同,但是数值上是相同的。
          根据以上,我们知道可以用一个指针指向数组的第一个元素,这样就可以用间接访问的方式去
          逐个访问数组中各个元素。这样访问数组就有了两种方式。
               有 int a[5];  int *p; p = a;
               数组的方式依次访问:a[0]  a[1]    a[2]    a[3]    a[4]
               指针的方式依次访问: *p   *(p+1)  *(p+2)  *(p+3)  *(p+4)
     5.5、指针与++ --符号进行运算
          指针本身也是一种变量,因此也可以进行运算。但是因为指针变量本身存的是某个其他变量的
          地址值,因此该值进行* / %等运算是无意义的。 两个指针变量相加本身也无意义,相减有意义。
          指针变量+1,-1是有意义的。+1就代表指针所指向的格子向后挪一格,-1代表指针所指向的格
          子向前挪一格。
          *p++就相当于*(p++),p先与++结合,然后p++整体再与*结合。
          *p++解析:++先跟p结合,但是因为++后置的时候,本身含义就是先运算后增加1 (运算指的是
                    p++整体与前面的*进行运算;增加1指的是p+1),
          所以实际上*p++符号整体对外表现的值是*p的值,运算完成后p再加1.
                      所以*p++等同于:*p;   p += 1;
                           *++p等同于 p += 1;   *p;
          (*p)++,使用()强制将*与p结合,只能先计算*p,然后对*p整体的值++。
          ++(*p),先*p取值,再前置++,该值+1后作为整个表达式的值。
          总结:++符号和指针结合,总共有以上4种情况。--与++的情况很类似。
     5.6、函数传参中使用指针
          int add(int a, int b)    函数传参使用了int型数,本身是数值类型。实际调用该函数时,
                            实参将自己拷贝一份,并将拷贝传递给形参进行运算。 实参自己实际是
                            不参与的。所以,在函数中,是没法改变实参本身的。
     5.7、什么是结构体?
          结构体是一个集合,集合中包含很多个元素,这些元素的数据类型可以相同,也可以不相同。
          所以结构体是一种数据封装的方法。
       结构体存在的意义就在于,把很多数据类型不相同的变量封装在一起,组成一个大的新的 数据类型。
       数据结构:把庞大复杂的数据用一定的方式组织管理起来,便于操作(查找,增加,删除等)这就叫
                 数据结构。
     5.8、结构体和数组的关联:
        数组是一种特殊的结构体,特殊之处在于封装内的各个元素类型是相同的。结构体和数组都是
          对一些子元素的封装,因此定义的时候都是封装作为整体定义,
       但是使用的时候,都是使用封装中的子元素。一般结构体变量和数组变量都不会作为一个整体操作。
     5.9、宏定义
          #define N (321)          //宏定义的格式
          宏定义要注意的问题:
          1、宏定义一般是在函数的外面
          2、宏定义必须要先定义,再使用宏。如果先使用就会编译报错。
          3、宏定义中宏名一般用大写,宏名后面的数值放到括号中。不是语法规定的,是约定俗成的。
          为什么使用宏定义?
             在C语言中,一般使用常数的时候,都不是直接使用,而是先把该常数定义为一个宏,
             然后在程序中使用该宏名。 这样做的好处是,等我们需要修改这个常数时,只需要在宏定义
             处修改一次即可。而不用到代码中到处去寻找,看哪里都用过该常数。
     5.10、枚举
               enum{     };

6.进阶
     6.1、空类型(关键字void)
          C语言中的void类型,代表任意类型,而不是空的意思。任意类型的意思不是说想变成谁就变
          成谁,而是说它的类型是未知的,是还没指定的。
          void * 是void类型的指针。 void类型的指针的含义是:这是一个指针变量,该指针指向
            一个 void类型的数。 void类型的数就是说这个数有可能是int,也有可能是float,也有可能
            是个结构体,哪种类型都有可能,只是我当前不知道。
          void型指针的作用就是,程序不知道那个变量的类型,但是程序员自己心里知道。程序员如何
            知道?当时给这个变量赋值的时候是什么类型, 现在取的时候就还是什么类型。这些类型
            对不对,能否兼容,完全由程序员自己负责。编译器看到void就没办法帮你做类型检查了。

          在函数的参数列表和返回值中,void代表的含义是:
          一个函数形参列表为void,表示这个函数调用时不需要给它传参。
          返回值类型是void,表示这个函数不会返回一个有意义的返回值。所以调用者也不要想着去
            使用该返回值。
          C语言设计基本理念:
              C语言相信程序员永远是对的,C语言相信程序员都是高手,C语言赋予了程序员最大的权利。
               所以C语言的程序员必须自己对程序的对错负责,必须随时脑袋清楚,知道自己在干嘛。
     6.2 、数据类型转换
               C 语言中有各种数据类型,写程序时需要定义各种类型的变量。这些变量需要参与运算。
       C语言有一个基本要求就是:不同类型的变量是不能直接运算的。 也就是说,int和float类型的变量
                           不能直接加减等运算。你要运算,必须先把两种类型转成相同的类型才可以。
          6.2.1 、隐式转换
          隐式转换就是自动转换,是C语言默认会进行的,不用程序员干涉。
                    C 语言的理念:隐式类型转换默认朝精度更高、范围更大的方向转换。
          6.2.2 、强制类型转换
          C 语言默认不会这么做,但是程序员我想这么做,所以我强制这么做了。
     6.3、变量
     变量,指的是在程序运行过程中,可以通过代码使它的值改变的量。
     6.3 .1、局部变量
     定义在函数中的变量,就叫局部变量。
     6.3 .1.1、普通局部变量(auto)
     普通的局部变量定义时直接定义,或者在定义前加auto关键字
void func1(void)
{
    int i = 1; 
    i++;   
    printf("i = %d.\n", i);
}
     局部变量i的解析:
          在连续三次调用func1中,每次调用时,在进入函数func1后都会创造一个新的变量i,
          并且给它赋初值1,然后i++时加到2,然后printf输出时输出2. 然后func1本次调用结
          束,结束时同时杀死本次创造的这个i。这就是局部变量i的整个生命周期。 下次再调
          用该函数func1时,又会重新创造一个i, 经历整个程序运算,最终在函数运行完退出
          时再次被杀死。
     6.3 .1.2、静态局部变量(static)
     静态局部变量定义时前面加static关键字。
 总结:
     1、静态局部变量和普通局部变量不同。静态局部变量也是定义在函数内部的,
        静态局部变量定义时前面要加static关键字来标识, 静态局部变量所在的函数在调用多
        次时,只有第一次才经历变量定义和初始化,以后多次在调用时不再定义和初始化,
        是维持之前上一次调用时执行后这个变量的值。 本次接着来使用。
     2、静态局部变量在第一次函数被调用时创造并初始化,但在函数退出时它不死亡,而是保
        持其值等待函数下一次被调用。下次调用时不再重新创造和初始化该变量, 而是直接用
        上一次留下的值为基础来进行操作。
     3、静态局部变量的这种特性,和全局变量非常类似。它们的相同点是都创造和初始化一
        次,以后调用时值保持上次的不变。不同点在于作用域不同
               6.3 .1.3、register关键字
               register(寄存器),C语言的一个关键字
               register int i = 3;
          总结:register类型的局部变量表现上和auto是一样的,这东西基本没用,知道就可以了。
                register被称为:C语言中最快的变量。
               C语言的运行时环境承诺,会尽量将register类型的变量放到寄存器中去运行(普通的
                变量是在内存中),所以register类型的变量访问速度会快很多。
              但是它是有限制的:首先寄存器数目是有限的,所以register类型的变量不能太多;其
                次register类型变量在数据类型上有限制, 譬如你就不能定义double类型的register
                变量。一般只在内核或者启动代码中,需要反复使用同一个变量这种情况下才会使用
                register类型变量。
          6.3 .2、全局变量
          定义在函数外面的变量,就叫全局变量。
                    6.3 .2.1、普通全局变量
               普通全局变量就是平时使用的,定义前不加任何修饰词。普通全局变量可以在各个文件中使
               用,可以在项目内别的.c文件中被看到,所以要确保不能重名。
                    6.3 .2.2、静态全局变量
               静态全局变量就是用来解决重名问题的。静态全局变量定义时在定义前加static关键字,
               诉编译器这个变量只在当前本文件内使用,在别的文件中绝对不会使用。这样就不用担心重
               名问题。所以静态的全局变量就用在我定义这个全局变量 并不是为了给别的文件使用,本
                就是给我这个文件自己使用的。
                    6.3 .2.3、跨文件引用全局变量(extern)
               就是说,你在一个程序的多个.c源文件中,可以在一个.c文件中定义全局变量g_a,并且可以
               在别的另一个.c文件中引用该变量g_a(引用前要声明)
          函数和全局变量在C语言中可以跨文件引用,也就是说他们的连接范围是全局的,具有文件
          连接属性, 总之意思就是全局变量和函数是可以跨文件看到的(直接影响就是,我在a.c和
          b.c中各自定义了一个函数func,名字相同但是内容不同,编译报错)。

     局部变量和全局变量的对比:
     1、定义同时没有初始化,则局部变量的值是随机的,而全局变量的值是默认为0.
     2、使用范围上:全局变量具有文件作用域,而局部变量只有代码块作用域。
     3、生命周期上:全局变量是在程序开始运行之前的初始化阶段就诞生,到整个程序结束退
        出的时候才死亡;而局部变量在进入局部变量所在的代码块时诞生, 在该代码块退出的
        时候死亡。
     4、变量分配位置:全局变量分配在数据段上,而局部变量分配在栈上。

 判断一个变量能不能使用,有没有定义,必须注意两点:
        第一,该变量定义的作用域是否在当前有效,是否包含当前位置;
        第二,变量必须先定义后使用。所以变量引用一定要在变量定义之前
基本概念:
作用域:起作用的区域,也就是可以工作的范围。
代码块:所谓代码块,就是用{}括起来的一段代码。
数据段:数据段存的是数,像全局变量就是存在数据段的
代码段:存的是程序代码,一般是只读的。
栈(stack):先进后出。C语言中局部变量就分配在栈中。
   6.4、多文件C语言项目
     6.4.1、简单的C语言程序(项目)只有一个C文件(a.c),编译的时候gcc a.c -o a,执行的
            时候./a
     6.4.2、复杂的C语言程序(项目)是由多个C文件构成的。譬如一个项目中包含2个c文件
            (a.c, b.c),编译的时候 gcc a.c b.c -o ab,执行的时候 ./ab
         实验:
在a.c和b.c中分别定义main函数,各自单独编译时没问题;但是两个文件作为一个项目 来编
gcc a.c b.c -o ab的时候,就会报错。multiple definition of `main' 为什么报错?
因为a.c和b.c这时候组成了一个程序,而一个程序必须有且只能有一个main函数。
          6.4.3、为什么需要多文件项目?为什么不在一个.c文件中写完所有的功能?
               因为一个真正的C语言项目是很复杂的,包含很多个函数,写在一个文件中不利于查找、组
               织、识别,所以人为的将复杂项目中的很多函数,
               分成了一个一个的功能模块,然后分开放在不同的.c文件中,于是乎有了多文件项目。
               以,在b.c中定义的一个函数,很可能a.c中就会需要调用。
               你在任何一个文件中定义的任何一个函数,都有可能被其他任何一个文件中的函数来调用。
               但是大家最终都是被main函数调用的,
               有可能是直接调用,也可能是间接调用。
          6.4.4、多文件项目中,跨文件调用函数
               在调用函数前,要先声明该被调用函数的原型。只要在调用前声明了该函数,那么调用时就
               好像这个函数是定义在本文件中的函数一样。
          总结:函数使用的三大要素:函数定义、函数声明、函数调用
               1、如果没有定义,只有声明和调用:编译时会报连接错误。undefined reference to
                   `func_in_a'
               2、如果没有声明,只有定义和调用:编译时一般会报警告,极少数情况下不会报警告。但
                  是最好加上声明。
               3、如果没有调用,只有定义和声明:编译时一般会报警告(有一个函数没有使用),有时
                 不会报警告。这时候程序执行不会出错, 只是你白白的写了几个函数,而没有使用浪费
                 掉了而已。

          实验:在一个项目的两个.c文件中,分别定义一个名字相同的函数,结果?
          编译报错 multiple definition of `func_in_a'
          结论:在一个程序中,不管是一个文件内,还是该程序的多个文件内,都不能出现函数名重复的
                情况,一旦重复,编译器就会报错。
               主要是因为:编译器不知道你调用该函数时到底调用的是哪个函数,编译器在调用函数时是
                           根 据函数名来识别不同的函数的。
          6.4.5、跨文件的变量引用
          (1)通过实验验证得出结论:在a.c中定义的全局变量,在a.c中可以使用,在b.c中不可以直接使
                            用,编译时报错 error: g_a undeclared (first use in this function)
          (2)想在b.c中使用a.c中定义的全局变量,有一个间接的使用方式。在a.c中写一个函数,然后函
             数中使用a.c中定义的该全局变量,然后在b.c中先声明函数,再使用函数。 即可达到在b.c中
             间接引用a.c中变量的目的。
          (3)想在b.c中直接引用a.c中定义的全局变量g_a,则必须在b.c中引用前先声明g_a,如何声明变
              量? extern int g_a;  
            extern关键字:
               extern int g_a;   这句话是一个全局变量g_a的声明,这句话告诉编译器,我在外部(程
                序中不是本文件的另一个文件)某个地方定义了一个全局变量 int g_a,
               而且我现在要在这里引用它, 告诉你编译器一声,不用报错了。

          问题:
1、我只在b.c中声明变量,但是别的文件中根本就定义这个变量,会怎么样?
答案是编译报错(连接错误)undefined reference to `g_b'
2、我在a.c中定义了全局变量g_a,但是b.c中没有声明g_a,引用该变量会怎么样?
答案是直接报错,未定义
               3、在a.c中定义,在b.c中声明,a.c和b.c中都没有引用该变量,会怎么样?
               答案是不会出错。只是白白的定义了一个变量没用,浪费了
         结论:不管是函数还是变量,都有定义、声明、引用三要素。其中,定义是创造这个变量或者函
                数,声明是向编译器交代它的原型,引用是使用这个变量或函数。 所以如果没有定义只有
                声明和引用,编译时一定会报错。undefined reference to `xxx'
     在一个程序里面,一个函数可以定义一次,引用可以有无数次,声明可以有无数次。因为函数定
     义或者变量的定义实际上是创造了这个函数/变量,所以只能有一次。( 多次创造同名的变量会造
     成变量名重复,冲突;多次创造同名的函数也会造成函数名重名冲突)。声明是告诉编译器变量/
     函数的原型, 在每个引用了这个全局变量/函数的 文件之前都要声明该变量/函数

     局部变量能不能跨文件使用?
     不能。因为局部变量属于代码块作用域。他的作用域只有他定义的那个函数内部。
     静态局部变量能不能跨文件使用?
     不能。因为本质上还是个局部变量。
     讨论跨文件使用问题,只用讨论全局变量和函数就可以了。
     6.5、常量
          常量,程序运行过程中不会改变的量。常量的值在程序运行之前初始化的时候给定一次,
          以后都不会变了,以后一直是这个值。
          6.5.1、#define定义的常量
              #define N 20         // 符号常量
              int a[N];
          6.5.2、const关键字
              const int i = 14
              const和指针结合,共有4种形式:
              const int *p; p是一个指针,指针指向一个int型数据。p所指向的是个常量。  
              int const *p; p是一个指针,指针指向一个int型数据。p所指向的是个常量。
              int *const p; p是一个指针,指针指向一个int型数据。p本身是常量,p所指向的是
                            个变量
              const int *const p;  p是一个指针,指针指向一个int型数据。p本身是常量,指向
                                   的也是常量。
结论和记忆方法:
1、const在*前面,就表示const作用于p所指向的量。所以这时候p所指向的是个常量。
2、const在*后面,表示p本身是常量,但是p指向的不一定是常量。
const型指针有什么用?
char *strcpy(char *dst, const char *src);
字符串处理函数strcpy,它的函数功能是把src指向的字符串,拷贝到dst中。
2.2.3、枚举常量
     枚举常量是宏定义的一种替代品,在某些情况下会比宏定义好用。
     enum
     6.6、头文件的引入
          6.6.1、为什么需要头文件?
               从之前可以看到,函数的声明是很重要的。当我们在一个庞大的项目中,有很多个源文件,
               每一个源文件中都有很多个函数, 并且需要在各个文件中相互穿插引用函数。 怎么解决函数
               的声明问题?靠头文件。
          6.6.2、#include包含头文件时,用<>和""的区别     
               <>用来包含系统自带的头文件,系统自带指的是不是你写的,是编译器或者库函数或者操作
                系统提供的头文件。
               ""用来包含项目目录中的头文件,这些一般是我们自己写的。

          6.6.3、防止重复包含头文件
               #ifndef __A_H__
               #define __A_H__
               // C语言头文件中的声明
               #endif
          6.6.4、写程序时,最好不要在头文件中定义变量。
               因为这时该头文件被多个源文件包含时,就会出现重复定义问题。全局变量的定义就应该
               放在某个源文件中,然后在别的源文件中使用前是extern声明。

你可能感兴趣的:(C学习)