C语言(知识点更新中)


第一节  初始C语言


一 什么是C语言

1 计算机语言

        计算机语言(包括:C、C++、Java、Python等) 是用于实现人与计算机交流的语言,而汉语、英语、日语等,适用于实现人与人之间交流的语言

        C语言,是面向过程的计算机编程语言广泛应用于底层开发,虽然也可用于上层软件开发,但存在一些缺陷,比如:内存安全问题、编写复杂度高、缺乏内置类型、代码可读性差、可移植性较差等问题,因此,在现代软件开发中,C语言已经逐渐被更加安全、高效、易用的编程语言所替代,如Java、Python、C#等

计算机系统层次结构图(简略版)

2 ANSIC国际标准

         为了避免各个开发厂商用的C语言语法产生差异而制定的(用来保证不同企业之间可以相互使用)

3 计算机语言的发展

        机器语言(二进制由0/1组成)--->汇编语言---------------------->B语言---------->C语言

        低级语言-------------------------------------------------------------------------------------->高级语言

4 实现第一个C语言程序

        (1)新建项目

      (2)点击空项目,输入项目名称,点击创建

        (3)在源文件处,右键,添加新建项

        4.选择C++文件,书写名称,并将后缀改为.c文件,点击添加,方可输入代码


第二节 数据类型


计算机单位

        bit(比特):一个二进制位(0或1)

        byte(字节):1byte = 8bit

        KB(千字节):1KB = 1024byte

        GB(吉字节):1GB = 1024KB

        TB(太字节):1TB = 1024GB

        PB(拍字节):1PB = 1024TB

内置类型

        不同的地方使用恰当的类型可以达到节省空间的效果(选择的类型决定了为变量开辟空间的字节数)

  1 整型家族

 (1)通过signed/unsigned来控制整型有无符号,除去char类型默认是不确定,由编译器来决定外,其余类型均默认为有符号型

        char:字符类型

        int:整型

        short:短整型

        long:长整型

        long long:更长的整型

 (2)有无符号数的取值范围

        1.char类型的区别

          (无符号数:255+1 == 0)

          (有符号数:-1+1 == 0)

        2.int类型的区别

        结果为:4294967286和-10

 (3)Limits.h,定义了整型数值的取值范围的相关信息(通过宏,定义了不同类型的最大最小值)

 (4)无符号数最好还是少用,容易出现死循环的风险,因为0-1等于最大值(无负数),可以想象为一个圆形

  2 浮点型家族

 (1)浮点数没有原反补的概念,且浮点数之间比较不可用 ==来比较 ((BV1og411P7Rf)视频中有讲解)

        float:单精度浮点型(4字节)

        double:双精度浮点型(8字节)

        long double(8/12/16字节,c语言未明确声明)

 (2) Float.h:定义了浮点数的取值范围的相关信息(宏定义了不同类型的最大最小值)

 (3) 浮点型在内存中的存储方式

浮点型在内存中的存储方式

 (4)浮点型在32位(4字节)存储形式:1+8+23

                         64位(8字节)存储形式:1+11+52

 (5) 演示示例

  3 构造类型

        数组: int arr[10],此时arr的类型:int [10]   

                    int arr[ ] = {0,1,2};此时arr的类型:int [3] 

        结构体类型:struct

        枚举类型:enum

        联合类型:union

  4 指针类型

        所有指针类型的大小均为4字节长度

        Int * pa;

        char * pr;

        float * pf;

        void * pv;

  5 空类型/无类型

        void,主要应用于参数、指针、返回值类型


第三节 变量和常量


一 变量

        用来描述变化的数据

1 定义变量的方式

        short age = 15;

        float weight = 55.5f;(55.5默认为double类型,加上f使其类型变为float)

        char ch = 'c';(单引号--字符;双引号--字符串)

2 局部变量和全局变量

局部变量全局变量
作用域局部变量只在其定义的作用域内可见,出了这个作用域就无法访问全局变量的作用域跨越整个程序,可以在程序的任何地方进行访问
生命周期局部变量的生命周期是从其定义处开始,到其所在作用域结束时结束全局变量的生命周期与程序的运行时间相同,从程序启动到结束
可见性局部变量的可见性也是有限制的,只能在其作用域内访问和修改全局变量对整个程序都是可见的,可以在任何函数内部进行访问和修改
默认初始化不会进行默认初始化,随机值

数值类型的全局变量会被初始化为0

指针类型的全局变量会被初始化为nullptr

类对象的全局变量会调用默认构造函数进行初始化

(1)同一项目的,其他文件中的全局变量不可直接使用,要加声明外部符号extern

(2)当全局变量和局部变量中有相同名称的变量时,若都可以使用的话,保持局部优先原则

3 变量的命名规则

二 常量

        用来描述不变的数据

1 常量的类型

        字面常量:可以直接写出来的,其中"abcd"为字符串常量

        const修饰的常变量,只是具有常属性,但本质上还是变量

        #define定义的标识符常量,命名时都为大写字母(无等于号;无分号)

                #define STR “love”---也可以是字符串

                #define ADD(x,y) (x+y)---定义宏---不要吝啬括号,其实最好写为((x)+(y))

        枚举常量:可一一列举的(一种自定义类型的方法)

                其中若使用printf("%d",red);//则此时打印结果为0

                使用示例:enum Color ch = red;


第四节 字符


一 字符串

        由双引号引起来的一串字符,其中\0为字符串结束的标志,若在字符串中间添加\0,则会提前结束

二 转义字符

        转义字符本质上为字符,打印时使用%c

(1)一些重要的转义字符

        \\:用来表示一个\字符

        \':用来表示一个'字符

        \":用来表示一个"字符

        \t:水平制表符,等价于tab键位的使用

        \n:换行符

        \ddd:其中d为八进制数字,最多为三位,例如\130代表为大写X

        \xdd:x为前缀必须要有,dd为16进制数字,无上限要求,例如\x42代表为大写B

(2)需要记忆的ASCII码

        \0:0

        0:48

        A:65

        a:97

(3)并非所有字符均可打印,其中包含不可打印字符


第五节 语句

        包含表达式语句、函数调用语句、控制语句、复合语句以及空语句(其中{}称之为代码块)


一 控制语句

        分支语句(选择语句)

        循环执行语句(循环语句)

        转向语句

二 选择语句

1 if else语句

(1)不加大括号的情况下,只能控制一条语句

(2)悬空else的问题:else总是和相距最近的if匹配

2 switch语句

(1)表达式中的值必须为整型

(2)可以嵌套使用

(3)常用于多分支情况

三 循环语句

1 while语句

        while(1)为死循环

2 do...while语句

        不适用于一次都不执行的场景(do...while循环体必执行一次)

3 for语句

(1)不要在for循环体中修改循环变量的值,否则for循环可能会失控

(2)建议采用前闭后开区间写法(for(int i = 0;i < 10 ; i++))

(3)continue在for循环语句中,可能因为i++在上面,不会跳过循环变量的变更

(4)三部分均可省略

          for(;;)

        此时由于判断部分为空,导致恒为真,等价于while(1)---死循环

(5)循环变量可能不止一个for(int i = 0 , int j = 0 ; i < 5 && j > 3 ; i++ , ++j)

4 break和continue

(1)break是直接跳出循环,但是若是多层嵌套循环,只能跳出一层循环

(2)continue是跳过continue之后的步骤,回到循环的判断部分

四 goto语句

(1)goto语句容易出现问题,通常不使用

(2)在跳出多层循环时使用goto,虽然也可使用多次break,但是会比较麻烦


第六节 函数


        函数的作用:简化代码,反复使用(且功能尽量单一)

一 库函数

        必须包含对应函数的头文件

1 I/O函数

1.1 标准输入函数scanf

        scanf()函数用于从标准输入设备读取格式化的数据

        scanf("%d%d",&a,&b);,若输入成功一个则返回1,输入成功两个则返回2输入失败返回EOF(-1)(EOF == 文件结束标志 == ctrl + z)

        scanf(“%d/%d”,&a,&b);此时在使用的时候,输入时中间必须使用(/)

1.2 标准输出函数printf

        printf()函数用于向标准输出设备打印格式化的字符串

        printf ("%d", num);,成功打印几个字符,返回值就是几

%c%d%u%hd%ld%lld
字符型整型形式打印无符号整型短整型长整型更长的整型
%f%lf%x%02x      %#x%p
单精度浮点型双精度浮点型16进制形式打印

按照16进制形式打印,对齐数为2,不足之处补0

按照16进制形式打印,但是会在开始加上0x

按照地址形式打印(16进制)(且前面的0也都会打印出来)

1.3 标准输入函数getchar()

        getchar()函数用于从标准输入设备读取单个字符

(1)ctrl+z会让getchar()和scanf返回一个EOF

(2)使用scanf("%s",str);只会读取 空格 之前的字符

(3)在需要消耗掉换行的时候使用getchar();

1.4 标准输出函数putchar()

        putchar()函数用于向标准输出设备打印单个字符

2 字符串操作函数

2.1 求字符串长度的函数strlen(str)

        从起始地址,向后查找一直到\0结束

(1)返回值类型为:unsigned int

(2)头文件:string.h

(3)模拟实现(3种方式)

2.2.1  字符串拷贝函数strcpy(str1, str2)

        拷贝str2的内容到str1中,拷贝到str2的\0位置处终止

(1)会将str2中的\0拷贝到str1中

(2)str1空间大小一定要够用(但也能运行,因为该函数会忽略这一点)

(3)返回值类型:char*(作用:为了实现链式访问printf(“%s”, strcpy(arr1,arr2)));

(4)模拟实现

2.2.2 字符串拷贝函数strncpy(arr1, arr2, n)

        拷贝str2中n个字节长度的内容到str1中

(1)会相对安全一些,有了字节大小的限制

(2)拷贝大小超过str2字符串长度的时候,以\0代替

2.3.1 字符串追加函数strcat(arr1, arr2)

        找到原字符str1中第一个\0,并从此处开始追加,追加至str2的\0处

(1)原字符串空间要足够大,可以容纳追加的

(2)模拟实现

2.3.2 字符串追加函数strncat(str1, str2, n)

        找到原字符str1中第一个\0,并从此处开始追加,追加str2中n个字节长度的内容

(1)追加结束后自动追加一个\0

(2)n大于str2长度时,只追加str2的所有内容,并在最后追加\0

(3)可以自己追加自己

2.4.1 字符串比较函数strcmp函数(str1, str2)

        根据ASCII对对应位置处进行比较,相同则后移,直至不同或到\0处

(1)规定的是<0 / >0 / ==0(vs中返回的是1/0/-1)

(2)模拟实现

2.4.2 字符串比较函数strncmp(str1, str2, n)

        只比较前n个字节看是否相同

(1)”abcd” 与 “aaaa”的比较,比较的是两者的地址(前者a和后者a的地址)

2.5 查找子串的函数strstr(str1, str2)

        看arr2是否是arr1的字串,若找到了返回str2第一次出现时的地址,找不到返回nullptr

(1)模拟实现

2.6 分割字符串的函数strtok(arr1,arr2)

(1)函数的执行过程如下:

        ①在第一次调用时,传入待分割的字符串作为第一个参数,并且返回被分割的第一个子字符串的指针。

        ②后续的调用中,第一个参数传入NULL,函数会继续返回被分割的下一个子字符串的指针,直到没有更多的子字符串为止。

(2)在每次调用strtok函数之后,原字符串会被修改,被分割的子字符串处会被'\0'替代

2.7 strerror函数

        将errno(全局变量,表示最近一次发生的错误码)转换为相应的错误信息字符串

        以上代码会尝试打开一个名为“file.txt”的文件。如果打开失败,就输出相应的错误信息字符串,否则就对文件进行操作并关闭它

(1)与perror作比较:

        perror会自动打印,且不能不打印,自动在字符串后加上冒号(输出结果:错误原因:+错误信息)

        strerror可以自行选择打印或者不打印

3 字符操作函数

3.1 字符分类函数
isalpha()判断字符是否为字母
isdigit()判断字符是否为数字
isalnum()判断字符是否为字母或数字
isxdigit()

判断字符是否是十六进制数字(0-9、A-F以及a-f)

isspace()判断字符是否为空格、制表符或换行符
isupper()判断字符是否为大写字母
islower()判断字符是否为小写字母
ispunct()判断字符是否为标点符号
isprint()判断字符是否可打印(是否为可见字符),包含空格
isgraph()判断字符是否是可打印字符,并且不是空格
iscntrl()判断字符是否是控制字符
3.2 字符转换函数

(1)大写转小写:int tolower(int c);

(2)小写转大写 :int toupper(int c);

4 内存操作函数

4.1 拷贝函数memcpy(str1, str2, n)

        拷贝str2中n个字节长度的内容到str1中

(1)可以拷贝任意类型的数据

(2)不可以实现重叠内存拷贝memcpy(str1+2, str1, 20);//1,2,3,4,5,6,7,8,9

        (会产生的结果:1,2,1,2,1,2,1,8,9)

        但实际很多编译器下memcpy也可以实现重叠拷贝(memmove >= memcpy)

4.2 拷贝函数memmove(str1, str2, n)

(1)可以实现重叠内存拷贝memmove(str1+2, str1, 20);//1,2,3,4,5,6,7,8,9

       (产生的结果:1,2,1,2,3,4,5,8,9)

(2)模拟实现:(str+m, str, n)从后向前拷贝

                           (str, str+m, n)从前向后拷贝

4.3 比较函数memcmp(str1, str2, n)

     ①比较n个字节,从两个地址依次向后比较,以字节为单位,直至不同处或比较结束

     ②返回值为:<0 / >0 / 0

4.4 内存设置函数memset(str, 0 ,20)

        从str地址处开始,每个字节都设置为0,共设置20字节

(1)第二个参数应该是unsigned char 类型(范围:0-255)

5 时间/日期函数

6 数学函数

7 其他函数

二 自定义函数

三 函数的应用

1 参数

(1)形参只是实参的一份临时拷贝,形参的修改不影响实参

(2)实参(变量、常量、表达式(a+b)、函数),不论是哪种都必须为确定的值

(3)形参:在函数为被调用时,形参不占用内存,形参在函数调用时创建,调用结束时销毁

2 函数的调用

(1)传值调用(不可更改实参数值)

(2)传址调用(可更改实参数值)

3 函数的嵌套使用/链式访问

(1)嵌套使用:例如,在main函数中使用printf函数

(2)链式访问:printf(“%d” , add(a,b) );

4 函数声明

        int ADD (int num1, int num2);

        int ADD (int, int);

(1)函数要先声明后使用

(2)在实际应用中,一般在头文件中创建 .h文件,用来存放函数声明

5 函数定义

        函数具体实现方式

(1)一般在源文件中创建 .c 文件,存放函数定义(实际应用中)

(2)在属性-常规-配置类型-静态库中,可以隐藏 .c 文件

        确保别人不会知晓函数具体实现(会生成 .lib 文件,替换掉 .c 文件)

(3)在分享给别人时,只需分享给 .h 和 .lib 文件即可

        使用时需(# pragma comment ( lib , “ xxx.lib”))和(#include “ xxx.h ”)

(4)(2)和(3)增加了保密性

6 递归函数

        把大型问题转化为与原问题相同的小规模问题来求解(大事化小),从而只需用少量的程序就可描述出所需要的多次重复计算,大大减少了代码量

(1)两个必要条件

        存在限制条件

        每次递归后,越来越接近于这个限制条件

(2)每次递归都是完整的独立的函数调用,不会因为其中某一次的return而结束

(3)每次函数的调用,都会在栈上开辟空间


第七节 数组

        一组相同类型元素的集合


一 一维数组

1 初始化

(1)整型数组初始化

(2)字符数组初始化

2 数组名和指针

(1)数组名只在以下两种情况代表整个数组,其余情况均为首元素地址(不论几维数组)

        计算整个数组的长度:sizeof(arr);

        取地址符后直接跟数组名:&arr;

(2)arr+1 ==  &arr[0]+1 ==  &arr[1]

(3)&arr+1,向后跳过整个数组大小个字节

(4)大小计算

        数组的大小:sizeof(arr) / sizeof(arr[0])

        区分:strlen(),遇到\0停止

3 其余注意事项

(1)数组长度不可为变量,只能为常量

          c99标准中增加了变长数组的概念,此时数组长度可以为变量,但并非所有编译器均可,且变长数组不可初始化

(2)一维数组在内存中是连续存放的,地址由低到高存放

(3)输入输出要结合循环语句进行(for/while/do..while)

二 二维数组

        可以理解为 存放元素为一维数组 的一维数组,例如arr[2][3],可以看作为存放2个arr[3]的一维数组

1 初始化

2 数组名和指针

(1)arr代表首行地址,arr == &arr[0]

(2)&arr[0]+1 ==  &arr[1],第二行的地址

(2)大小计算

        数组大小:sizeof(arr) / sizeof(arr[0][0])

        行数计算:sizeof(arr) / sizeof(arr[0])

        列数计算:sizeof(arr[0]) / sizeof(arr[0][0])

        第一行大小:sizeof(arr[0])

3 其余注意事项

(1)二维数组在内存中是连续存放的,地址由低到高存放

三 数组的应用

1 作为函数参数的时候

(1)一维数组

        非指针形式:void fun(int arr[])

                一维数组(数组长度可以省略,长度错误也没错(不推荐))

        指针形式:void fun(int* arr)

(2)二维数组

        非指针形式:void test(int arr[][5]);

                二维数组(数组行数可以省略)

        指针形式:void test(int (*parr)[5]);


第八节 操作符

        计算时都使用的是补码


一 算术操作符

(1)加法:+

(2)减法:-

(3)乘法:*

(4)除法:/

(5)取余:%(两端都必须为整数)

二  移位操作符

        必须为整数(负整数结果不确定,不推荐使用)

(1)左移操作符:<<(左边(符号位)丢弃,右边补0)

(2)右移操作符:>>(右边丢弃,左边补上)

        算数右移:左边补符号位

        逻辑右移:左边补0(与编译器相关)

三 位操作符

(1)按位与:&(有0为0,同1为1)

        (n & (n - 1),每使用一次就会减少一个二进制中的1)

(2)按位或:|(有1为1,同0为0)

(3)按位异或:^(相同为0,不同为1)

四 赋值操作符

(1)=

(2)+=

(3)-=

(4)/=

(5)&=

(6)^=

(7)|=

(8)>>=

(9)<<=

五 单目操作符

(1)逻辑反操作符:!

        非0为真,!5 == 0,!0 == 1

(2)取地址操作符:&

(3)sizeof操作符:sizeof(a) == sizeof a == sizeof(int)

        因为可以写为siezof a,所以证明sizeof非函数,函数的()不能省去

(4)按位取反:~

        符号位也会被按位取反

        while( ~scanf( “%d” , &num)) {  },当返回EOF(-1)的时候,再按位取反等于0,所以此时会终止while循环

(5)++

(6)--

        不要过于困难化,例如b=(++a)+(++a)+(++a),导致在不同编译器结果不同,所以是错误代码

(7)强制类型转换

(8)解引用操作符:*

六 关系操作符

(1)>  

(2)>=

(3)<

(4)<=

(5)!=

(6)==

七 逻辑操作符

(1)&&  逻辑与(一假为假)

(2)||     逻辑或(一真为真)

八 条件操作符

        exp1 ? exp2 : exp3(若exp1为真,则计算exp2,为假则计算exp3)

九 逗号表达式

        从左向右依次计算,但整个表达式只保留最后一个的结果

        (b=(a=3+1,c=1+1,a-c)),则a=4,c=2,d=2,但是b的值=(a-c),只看最后一个

下标引用、函数调用和结构成员

(1)下标引用[]

        2个操作数:数组名+数组个数

(2)函数调用()

       至少一个操作数:函数名+未知个数参数 ,Add()

(3)结构成员访问操作符

        .

        ->

十一 原反补

(1)原码

(2)反码:符号位不变,其他位按位取反

(3)补码:反码+1(可能影响符号位,先不管)

        补码->原码:

                ①补码-1再按位取反

                ②补码按位取反再+1(补码的补码=原码)

(4)正整数:原码 == 反码 == 补码

(5)最高位:0表示正数,1表示负数

(6)原码反码补码存在的意义

        整数在内存中存储的形式:补码的二进制形式

        整数打印出来的形式:原码的二进制形式

        存放补码的原因:CPU中只有加法计算器

(7)原码计算1-1 == -2

         补码计算1-1 == 0(最高位放不下,直接舍弃,最终为0)

十二 隐式类型转换(整型提升)

(1)长度小于整型(int型)的,在进行计算前,先提升为int再去计算

(2)有符号数:高位按符号数提升

(3)无符号数:高位补0

十三 算术转换

(1)当 >= int时,计算时按照最长的类型进行计算

        long double > double > float > unsigned long int > long int > unsined int > int

十四 操作符的属性

(1)优先级

(2)结合性

(3)是否控制求值顺序(&&  ||  逗号  三目操作符)

(4)注意事项

        ①并非依靠三者可以确定最终计算顺序(根据编译器而定),若有结果不同,则表达式存在问题

        ②少写过于复杂的++/--运算


第九节 关键字

        关键字是C语言中本身存在的,不可以创造关键字


一 关键字

        auto

        char / int / short / long / double / float / signed  /unsigned

        if / else / do / while / for / switch / case / break / continue / default

        const / static / typedef / sizeof / union / enum / struct

        goto / extern / return / void

        volatile / register

二 注意事项

        include/difine:都不是关键字,都是预处理指令


第十节 静态变量


一 修饰局部变量

(1)改变了存储类型,本身在栈区,修饰后存储在静态区

(2)生命周期延长,与程序生命周期相同

(3)作用域不会改变,只在原本作用域生效,作用于外使用要使用指针

(4)static修饰的变量可以更改

(5)不初始化的话默认为0

while(1)
{
         Static int num = 0;
         num++;
         printf("%d",num);
}

//此时打印结果为:1,2,3...,因为只执行一次

二 修饰全局变量

        全局变量本身有外部连接属性,所以可以使用extern

        static修饰后改为内部连接属性,只能在本.c文件中使用(只想自己使用时加上static)

三 修饰函数

        同全局变量,本身具有外部连接属性,可用extern

        static修饰后改为内部连接属性,只能在本.c文件中使用static int ADD (int a,int b){ }


第十一节 指针

        指针 = 地址 = 内存编号


一 指针简介

(1)每个内存单元为1字节(8bit)

(2)指针变量的大小

        任何类型的指针大小均是4字节(32位机器是4字节;64位机器是8字节)

(3)指针类型

        决定了解引用时的访问权限(int以4个字节为单位进行访问)

        决定了指针+1或-1所走的步伐大小( +n = n * sizeof( int ) )

        注:整数和浮点数在内存中存储方式不同,虽然有的都是占用4个字节,但解引用的话结果不可知

(4)指针运算

        指针 ± 整数:根据指针类型(向前/向后)跳转相应的字节

        指针的关系运算:&arr[0] < &arr[1]

        指针-指针:(必须是同一块空间的指针)&arr1[5] - &arr1[2],得到中间的数据个数3

                              错误示例:&arr1[5] - &arr2[0],arr1

二 一二级指针的使用

(1)int * pa = &a;

        *表示:是指针变量

        int表示:pa指向的对象是int类型

(2)*pa = 20;

      *表示:解引用操作

      *pa = 20;表示:将a 的值改为20

(3)二级指针

(4)字符指针注意事项

                

        *p1 = ‘w’;  //会出错,因为不可修改

        arr中的 abcdef 不是常量字符串,相当于创建好空间来存放字符串abcdef

        p1和p2,是指针,指向该常量字符串abcdef

三 指针数组

        存放指针的数组

        整型数组:int arr1[5] = {1 , 2, 3 , 4, 5 };  //类型:int

        指针数组:int* arr2[3] = { &a , &b , &c};  //类型:int*

(1)传参形式:

        非指针形式:同数组,void test(int *arr[]),数组长度可以不写

        指针形式:void test(int** arr);

四 数组指针

        指向数组的指针

(1)int (*p)[10] = &arr;   //指向:int[10]([10]不可省略)的指针

(2)二维数组:int (*p)[3][5] = &arr;

(3)*p == arr ==&arr[0](首元素地址)

(4)数组指针不常用于一维数组,使用起来太过冗余

五 函数指针

(1)示例演示,假设有一个Add函数(fun和&fun不仅数值相同,含义也相同,不同于数组名arr)

        ①int (*p) (int , int) = Add;

        ②int (*p) (int , int) = &Add;

(2)函数指针类型的重命名:

        typedef int (*pA)(int , int); //原类型为:int(*)(int , int)

        使用 typedef 定义函数指针类型可以简化代码并提高可读性

六 函数指针数组

(1)int (*arr[4])(int , int);

        数组名:arr

        数组大小:4

        数组元素类型:int(*)(int , int)

(2)应用示例

七 指向函数指针数组的指针

八 回调函数

        把函数当作参数使用

九 野指针

(1)出现的原因

        指针未初始化(int *p;且在之后访问指针p)

        指针越界访问(数组)

        指针指向空间被释放

(2)如何避免

        指针初始化(int * p = NULL;)或(int * p = &a;)

        小心越界访问

        指向空间释放后立即置为NULL

        避免返回局部变量地址(局部变量出作用域后会被释放)

        使用前检测指针的有效性( if(p != NULL){   } )


第十二节 结构体

        用来描述复杂类型


一 结构体简介

(1)结构体演示实例:

(2)结构体中的结构体指针类型

(3)打印(两种方式)

        ①printf(“%s,%d,%s,%lf”,LiMing.name,LiMing.age,LiMing.sex,LiMing.score);

        ②struct Stu * ps = &LiMing;

           printf(“%s,%d,%s,%lf”,ps->name,ps->age,ps->sex,ps->score)

(4)输入

        scanf(“%s, %d, %s, %lf”, LiMing.name, &(LiMing.age), LiMing.sex,&(LiMing.score);

        scanf(“%s, %s, %lf”, LiMing.name, LiMing.sex, &(LiMing.score)); //跳过年龄

(5)修改

        s1.age = 20;//可以直接修改

        字符串的内容不可直接修改,需要通过strcpy或者scanf修改

二 匿名结构体

        只能使用一次,创建的时候有几个变量,就能用几个,在之后不可再进行创建

结构体的自引用

        主要应用于链表

四 内存对齐

1 概念

(1)首个成员放在结构体偏移量为0的地址处

(2)其他成员从对齐数的整数倍地址处存放(对齐数:默认对齐数和成员大小的较小者)

(3)结构体大小必须为所有成员对齐数最大者的整数倍

(4)嵌套结构体的对齐数 = (内部所有成员的最大对齐数)

(5)拥有嵌套结构体的结构体的最终大小:所有对齐数的整数倍(要考虑到嵌套结构体对          齐数)

2 内存对齐的原因

(1)平台原因:并非所有的平台都可以在任意地址读取任意类型的数据

(2)性能原因:未对齐处理的需要两次内存访问,对齐的只需一次(用空间换时间)

3 节省空间的办法

        尽量使占用空间小的放在一起

4 修改默认对齐数

        用于默认对齐数不适合时使用,#pragma pack()

5 计算结构体成员相对于结构体起始地址的偏移量

        offset(struct S1, num);

五 结构体传参问题

(1)传参时要进行传址传参,节省空间

(2)传值传参:一大片空间,空间浪费大;安全性相同

(3)传址传参:4/8字节空间,节省空间;+const后安全性相同

原因:①函数传参时,参数进行压栈(进栈),若结构体过大,压栈致使系统开销过大,性能低下

           ②传参结束后调用函数,每次函数调用要在栈区开辟一块内存空间

六 柔性数组

        结构体中最后一个元素允许是为止大小的数组,该数组称之为柔性数组(任意类型都可以,结构体类型的数组也可以)

(1)特点

(2)柔性数组与指针方式的对比

柔性数组指针

开辟一个动态内存空间

(方便内存释放)

开辟两个动态内存空间

开辟空间时连续的

(读取数据的时候,操作系统会预读取周围数据,以方便快速访问)

(连续空间更有利于节约空间,多次申请开辟空间,会产生更多的内存碎片)


扩展阅读:C语言结构体里的数组和指针(酷客)


第十三节 位段


一 使用方式

(1)类似于结构体

(2)采用不对齐的方式存储

(3)成员只能为:signed int / unsigned int / char类型

(4)成员后面:必须有冒号+数字

        只能在可以满足需求的时候适当使用:此时的a只占用2bit的空间,只能存放00/01/10/11四种数据

二 内存分配

(1)signed int / unsigned int会以4字节空间来开辟

(2)char会以1字节空间来开辟

(3)不可跨平台,在可移植的应用程序上避免使用位段

三 跨平台需要考虑的问题

(1)int默认有无符号的问题

(2)int在不同平台占用空间的大小2/4/8

(3)成员在使用空间的时候,从左到右/从右向左的

(4)是否舍弃不足以存放的空间

四 位段的应用

        每行都是一个int型(32bit),且空间变小后,网络速度也快


第十四节 枚举

        将可能的取值一一列举出来


一 使用方式

二 枚举的优点

        便于调试,不同于#define,在调试之前不会将变量名直接替换为等价的式子/数值,不会导致看到的和应用的不同的现象发生


第十五节 联合体/共用体


一 使用方式

        根据联合体的特点,可以用来判断大小端

二 特点

(1)成员共用同一块内存空间

(2)因为共用同一块内存空间,所以一般同一时间只会使用一个,因为修改其中一个其他的也会被修改

(3)联合体的大小最小是最大成员的大小

(4)存在内存对齐

(5)联合体的存在的目的,并非是为了节省空间

三 内存对齐


第十六节 动态内存管理


一 动态开辟内存的原因

        非动态申请内存空间char arr[40]

动态申请内存空间malloc

malloccallocrealloc
申请的内存连续可用,返回值为指向该空间的指针,若申请失败,返回值为NULL同malloc

①后面空间足够时,在原来空间后面直接追加,并返回原来位置

②后面空间不足时,重新在内存中寻找一块区域存放,并复制原来空间内容,释放掉原来空间,返回新申请空间的起始地址

不会进行初始化,为随机值

会进行初始化,默认初始化为0

不会进行初始化,为随机值
需要判断是否申请成功同malloc

扩容失败的时候返回NULL,所以不要使用起初地址,防止原空间被搞丢

malloc(0):c语言中并未定义,具体结果取决于编译器

realloc(NULL,20)等价于malloc(20);

动态内存的释放和回收free(p)

(1)在进行释放的时候,自会知晓malloc申请了多大的一块内存空间

(2)p指向的空间,必须为动态开辟出来的,否则会出错

(3)p若为NULL指针,则无意义,啥都不干

(4)若在malloc开辟之后,不进行free操作进行释放,

        ①若程序结束,自动回收malloc开辟的空间

        ②若程序不结束,则不会自动回收,会造成内存泄漏(开辟空间无法回收被浪费)

(5)free释放掉内存后,p仍指向该空间,形成野指针(所以需要令p=NULL)

(6)当malloc申请空间后,未被free,情况频繁发生,直至内存被耗干时,电脑会死机

(7)现在电脑都会有保护机制,允许应用最多占用?GB的空间,来避免( 6 )问题的发生

四 易错点

(1)对NULL指针的解引用操作

        申请空间后,判断空间是否申请成功

(2)对动态开辟空间的越界访问

        对内存边界主动进行检查,类似于数组越界访问

(3)对非动态开辟内存进行free释放(直接出错)

(4)使用free释放动态开辟空间的一部分空间(直接出错)(造成部分空间内存泄漏,释放不了)

(5)对同一块空间多次释放(直接出错)

(6)动态开辟内存忘记释放(内存泄漏)

        未使用free释放

        开辟后,指向空间的指针被销毁,找不到申请的空间


第十七节 文件操作


一 文件简介

1 为什么使用文件

(1)当数据放在内存中的时候,程序退出,数据将自动销毁,再次使用时,需要重新录用

      (不具备数据持久化)

(2)使用文件操作,我们将数据放入到硬盘之中(做到了数据持久化)

2 文件的分类


二 文件指针

(1)在操作文件的时候,会自动创建并填充一个文件信息区(FILE类型的变量)

      (文件信息区一般包括:文件名称、状态、位置等)

(2)此时我们会创建一个FILE类型的指针,指向该文件信息区(FILE* pf)

(3)此时便可以通过文件指针很方便的找到与之相关联的文件

             ①(文件指针  -->  文件信息区  -->  与文件本身相关联)

             ②(文件指针              ----->             与文件本身相关联)

三 文件的打开和关闭

(1)文件打开和关闭配套使用,类似于malloc和free

     ①文件打开:自动创建并填充好文件信息区,并在最后返回起始地址

     ②文件打开失败:返回NULL指针(需要判空操作)

     ③fopen(“(文件的名称:位置+主干+后缀)”, ”(文件的打开方式)”)

     ④fclose(NULL);//会直接出错

     ⑤使用”a+”,并不会销毁掉原本文件中的内容

     ⑥打开方式(具体包含)

       “w”:会在打开文件的时候,自动销毁之前的内容,致使打开的是一个空白文件

(2)具体使用

四 文件的顺序读写

1 相关函数



2 字符串输出函数fputc



3 字符串输入函数fgetc


4 文本行输出函数fputs


5 文本行输入函数fgets

6 格式化输出函数fprintf

        要对比printf进行记忆


7 格式化输入函数fscanf

        对比scanf进行记忆


8 二进制输出fwrite

        以二进制形式写入,所以出现乱码

9 二进制输入fread

        返回值:实际读取到的元素的个数

        fread进行输入的要类型匹配

10 区分scanf / fscanf / sscanf 和printf / fprintf / sprintf

注意事项


main函数(主函数)

1.有且仅有一个,且在一个工程中也只能有一个

2.老式写法为void main,现在大都改为int main

3.程序执行从main函数开始执行

二 注释

        两种注释方式: /*  */  以及 //

        /**/为C语言的注释风格,但是例如/*a  /*  b */  c */,并不会注释掉c中内容,因为存在此缺陷,所以引用//(C++注释风格)

三 快捷键总结

        ctrl + F5        (编译+链接+运行)

        F5                 (有的编译器会出现一闪而过的情况)

大小端字节序存储

(1)大端:高位放低地址,低位放低地址

(2)小端:高位放高地址,低位放低地址

正常存放:12 34 56 78(我们的理解)

内存中存放:78 56 34 12(小端存放)

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值