C语言学习笔记

发布时间 2024-01-05 13:12:39作者: Qing-Huan

1. 数据类型

重点

  • return 先返回函数结果,后结束当前函数
    返回函数结果:返回值放在eax寄存器中,然后ret返回
  • 功能不同的代码之间要空行
  • 一份代码注释风格不要多样化
  • /**/ 块注释不能嵌套
  • long long 是C99里面定义的
  • extern int data;
    • 编译器理解为:该变量是int类型,来自于其他文件

关键术语

声明:不会分配内存空间 :仅告知编译器有一个int类型的num变量再后面定义
定义:会开辟内存空间
使用:即为读、写
初始化:声明时赋初始值

extern int num;
void test()
{
    cout << num << endl;
}
int num; // 定义才会分配内存空间

变量名

  • 数字、字母、下划线,开头不能用数字,不能用关键字
  • 类型决定了内存的空间大小,变量名代表空间内容

字符类型

  • a ~ z 97 ~ 122
  • A ~ Z 65 ~ 90
  • 大小写字母相差32
  • char类型初始化最优写法是:char ch = '\0';

字符类型可以存无符号类型,真正存入内存的值是ASCII码值
计算机存的都是数值,只是输出时表现形式是字符而已
仅是开辟了一个字节的空间,存什么都可以

image

  • 字符常量和字符串常量的区别仅为 单引号双引号 之分
  • 'a'占一个字节【a】,"a"占两个字节【a和\0】
  • 双引号的作用:1. 描述为字符串 2. 取该字符串的 首元素 的地址
  • 单引号的作用:1. 描述为字符 2. 取该字符的ASCII码值
  • (int num = '1234';)
    https://www.cnblogs.com/qinghuan190319/p/16822279.html

实型变量

  • C++中不以f结尾的浮点数默认为double
  • float类型初始化最优写法是:float f = 0.0f;
  • C++的cout不管是float还是double,都只输出小数点后5位

image

转义字符

普通转义:'\0' '\n' '\t' '\r'(回到行首) '\a'(发出警报)
高级转义:

  • '\ddd' 每个d的范围必须是 0 ~ 7,3个d表示最多识别3位八进制数据
  • '\xhh' 每个h的范围必须是 0 ~ 7 a ~ f ,2个h表示最多识别2位十六进制数据,或许能识别3位,但是会溢出

数据类型转换

  • 编译器自动完成,小空间自动往大空间转,目的保证 精度不丢失
  • charshort 只要运算了,都自动转化为4字节,就算是char和char运算也转化为4字节
  • unsigned 类型与 signed 混合运算,会先将 signed 类型转化为 unsigned 类型,再进行运算。如果只关心结果,就计算完之后让它以 unsigned 类型输出。

结构体

非基本类型(char short int long float double unsigned signed),即为构造类型(数组、结构体、枚举、共用体)

struct 类型名
{
    int num; // 成员 不要初始化,c99以上可以初始化
    int name[32];
};//有分号
  • 结构体数组清零
    memset(arr, 0, sizeof(arr));

  • 结构体变量成员的偏移量:&((struct stu*)0)->num

  • 相同类型结构体赋值操作,是 memcpy 的操作
    image

  • 结构体变量中有指针成员才有可能出现浅拷贝和深拷贝的问题
    结构体变量 自身空间 内容会完全拷贝。变量内若带有指针指向 其他空间其他空间 不会被拷贝,此时两个结构体变量的指针指向同一个 其他空间

    • 可能会导致同一个空间被释放两次
    • 在统计数据的时候,将结构体中的数据拷贝到总结构体时,指针指向的空间会被第二次取值覆盖。
  • 结构体内存对齐

    1. 确定分配单位长度
    2. 确定每个成员的偏移量
    3. 总大小为分配单位的倍数

    double存储时,可能会折半存储。如下结构体可能是16字节,也可能是12字节(double存两行)

    struct A
    {
        char a;
        double b;
    }
    
  • 结构体嵌套结构体内存对齐
    结构体

struct A
{
    char a;
    int b;
    short c;
};

struct B
{
    char d;
    struct A e;
    short f;
}
  • 强制对齐规则
    #pragma pack (value)
  1. 确定分配单位长度
    min(结构体中最大的基本类型长度, value) 为分配单位
  2. 确定每个成员的偏移量

位域

  • 不能对位域取地址:系统为每个字节分配地址,一个字节只有一个地址编号,没有给每个字节分配单位
  • 位域不能超过自身字节数
  • 位域标准写法一定是无符号int类型,但无符号或有符号的char、short、int、long也不会报错的。
  • 给位域赋值,尽量不要超过位域自身大小。只识别低位,高位赋值前溢出,不会把前面的覆盖哦~
  • 若声明为有符号类型,读取时会按有符号数据读出
  • 相邻类型相同的位域可压缩,压缩位数不能超过自身类型的二进制位数
  • 另起一个存储单元方式:unsigned char : 0;
  • 无意义位段:unsigned char : 2;

共用体 union

所有成员共享同一块空间,空间由最大的类型长度决定。

枚举 enum

将枚举变量想要赋的值一一列举出来,默认从0递增,若某元素被赋初值了,后面的元素值从此刻递增,值可以重复。
enum XXX{符号常量, 符号常量, 符号常量};
XXX类型的变量,只能赋括号中的值。(C语言不报错,C++会报错)

2. 进制转化计算

进制输入输出

标准C语言不支持二进制输入,有些高版本编译器支持

进制 输入 输出
二进制 0b #include <bitset>
bitset<8>(X)
八进制 0 oct
十进制 (默认) dec
十六进制 0x hex

进制转化:数据本身不变,仅表现形式不同

  1. 十进制转二进制、八进制、十六进制
    短除法

  2. 二进制、八进制、十六进制转十进制
    位次幂

  3. 二进制转八进制、十六进制
    每3位二进制 对应 1位八进制
    每4位二进制 对应 1位十六进制

  4. 八进制、十六进制转二进制
    每1位八进制 对应 3位二进制
    每1位十六进制 对应 4位二进制

  5. 八进制与十六进制之间没有直接转化方法
    通过二进制转化

???

image

问题1:为什么前面给定了oct后面的数据流也是八进制?

3. 原码反码补码

概念

原码:计算机对数据的二进制直接的表达形式

原码、反码、补码转化规则

  • 无符号数:原码 == 反码 == 补码
  • 有符号正数:原码 == 反码 == 补码
  • 有符号负数:
    • 反码 == 原码符号位不变,其他位按位取反
    • 补码 == 反码 + 1

补码的意义

  1. 将减法运算变为加法运算
  2. 统一了 0 的编码
    1000 0000 = -0
    0000 0000 = +0
    +0和-0都是低电平,所以-0没有意义,被计算机当为-128
    

数据存储

  • 计算机以补码形式存储数据
  • 八进制、十六进制没有负数概念,以原码存储

-10 存入栈区的值是 0xFFFF FFF6 (10的补码),并且从反汇编中能看到赋值命令的立即数是 0xFFFF FFF6
image

数据读取

  • 无符号变量、八进制、十六进制 取值,输出内存的 原样 数据
  • 有符号变量 取值,看内存的最高位是否为 1 ,如果为 0 原样输出,如果为 1 则对内存数据 再取一次补码

unsigned int data = -10;

-10 由unsigned类型数据读出时,输出 FFFF FFF6,强转为signed类型时,输出-10。那么它在内存中到底是什么呢?

image

4. 关键字

const、register、volatile、sizeof、typedef

1. const

  • const 修饰变量 只读
  • 不能通过变量名去改值,但是可以通过指针间接修改空间数据。

1. 以常量初始化const变量

const int data = 10;
  1. 如何去定位开辟空间的动作?
    怎么去参考?
  2. 会不会是立刻开辟了,不能让用而已

C++概念:系统 不会立即data 开辟空间,而是将 10 放入 符号常量表 ,一旦用户取 data 的地址,系统立刻给 data 开辟空间。
C与C++的const区别:const 变量存储在只读数据段,可以通过指针修改,而C++的const在编译阶段会将变量替换为常量,达到真正的不可修改。

image

  • 声明一个const类型的局部变量,观察到 data 的存储位置在栈区,理论上栈区是可读可写的,因此我们使用指针能够修改此空间数据是没有疑议的,但是最后输出 data 时,却并不是这个值。。。

image

运行结果:*p = 1000, data = 10

【自释义:存在两个data,一个在栈区,一个是变量名与值直接映射的,取地址使用的是栈区的,变量名用的是映射的】
【二次探究:只有栈区一个data,在编译阶段,就将const变量在代码段进行了替换,我们取地址赋值的是栈区的data,所以最终的结果不一样】

image

  • 声明一个const类型的全局变量,观察到 a 的存储位置明显远离了 b、c,运行结果:报错(引发了异常:写入访问权限冲突)【自释义:该只读变量存储空间的页表是只读的】

image

2. 以变量初始化const变量

int b = 10;
const int data = b;

系统 立即data 变量开辟空间,没有符号常量表的概念

3. const和指针结合

  • 3.1 const int *p 和 int const *p
    如果const在*左边,修饰*p为只读,不能通过*p修改p指向的空间的值

  • 3.2 int * const p
    如果const在*右边,修饰p为只读,不能修改p,也就是p不能指向其他空间了,但是可以通过*p修改p指向的空间的值

  • 3.3 const int * const p 和 int const * const p
    不可修改*p,也不能修改p指向的空间

2. register

  • 高频使用的变量,可定义成寄存器变量
  • 就算定义了register变量,也有可能不存入寄存器。编译器 编译期 发现某个变量被高频使用,就算没有register修饰,也会定义成寄存器变量

寄存器变量不能取地址:C++优化了不报错,C会报错

3. volatile

每次从内存取数据,防止编译器优化。

4. sizeof

是计算该类型定义的 变量 时所占的空间,和 strlen 区分开

sizeof('a') === 4
被看成'a'的ASCII码值,当成了97,所以是int类型

5. typedef

  • 为已有的类型取别名,不能创建新的类型,不会影响原有的类型
  • 别名要全部大写,见名知意
  • 使用方法:
    1. 已有的类型 定义一个变量
    2. 使用 别名 替换变量名
    3. 在表达式的最左边加上关键字 typedef
  • 示例:
    typedef int ARRAY_TYPE[5];
    ARRAY_TYPE data = {1,2,3,4,5};
    

6. static

变量不能给全局区的变量初始化

5. 运算符

算术运算符

  • /

    • 当运算符对象都是整型,它是取整
    • 当运算符对象中有实型,它是除法
  • %

    • 两边运算符 不能 为实型
  • += -= *= /= %= &= ^= |= <<= >>=

    • 复合运算符一定要将 = 右边看成一个整体,从右向左算
      eg: a*=b+5 -----> a = a*(b+5)

关系运算符

  • > < == >= <= !=
    • 用于数值的判断
      反eg:"haha" > "heihei" ---> 比较的是两个字符串的地址

逻辑运算符短路特性

&& 短路特性:遇到假即为假,不会判断下一组表达式

image

|| 短路特性:遇到真即为真,不会判断下一组表达式

image

! 非0即为真

位运算符

位运算符 规则 应用 备注
& 全1为1 指定位 置0 0 运算
` ` 全0为0 指定位 置1
^ 同0异1 指定位 翻转 1 运算
eg: data为1字节,将data的第3、4位清零,其他位保持不变【第3、4位是从右向左数,首位为0】
data &= ~(0x01 << 4 | 0x01 << 3)

eg: data为1字节,将data的第5、6位置1,其他位保持不变
data |= (0x01 << 5 | 0x01 << 6)

位移运算

  • 仅负数的算术右移,高位补1
  • 当位移长度大于变量宽度时,真正位移的位数先对宽度取余

image

三目运算符

  • 表达式?值1:值2 表达式为真,取值1,为假取值2

image

运算符优先级(重要)

image

自增自减运算符

  1. ++i i++ 作为独立语句,两者没有区别
  2. 混合运算时,若 ++ -- 在右边,先使用,后运算
    • eg: j = i++; // j = i; i++;
  3. 混合运算时,若 ++ -- 在左边,先运算,后使用
    • eg: j = ++i; // i++; j = i;

image

逗号运算符(优先级最低)

int num = 0;
num = 10, 20, 30, 40;
cout << num << endl;    // 10

num = (10, 20, 30, 40);
cout << num << endl;    // 40

image

当逗号运算符遇到括号时,从左至右运算,到达最后一个表达式时,会按照优先级判断。
image

6. 常用库函数

随机数

序号 函数 功能
01 time_t time(time_t *tloc); <time.h> 获取当前时间
(从1972年到现在所经理的所有描述)
02 void srand(unsigned int seed); <stdlib.h> 设置随机数种子
不要放在for循环里
03 int rand(void); <stdlib.h> 通过返回值 产生随机数
#include <stdlib.h>
#include <time.h>
void test05()
{
    // 先设置随机数种子(一般以时间为种子)
    // time(NULL) 获取当前时间(从1972到现在所经历的所有秒数)
    srand(time(NULL));

    // 产生一个随机数
    cout << "随机数:" << rand() << endl;
    cout << "随机数:" << rand() << endl;
}

动态内存操作

#include<stdlib.h>

序号 函数 功能
01 mallo 申请内存空间
02 calloc 申请空间后清零
03 realloc 追加/缩减内存空间(可传负数)
04 free 释放内存空间
  • 不能对已释放的空间再次释放,防止free多重释放
    if(ret != NULL)
    {
        free(ret);
        ret = NULL;
    }
    

交换两个字符串,调用三次strcpy???

字符串操作函数

#include <string.h>

序号 函数 功能
01 strlen 测量字符串长度
02 strcpy 字符串拷贝
03 strncpy 拷贝字符串前n个字符
04 strcat 字符串追加
05 strncat 从第n个字符追加
06 strcmp 字符串比较
07 strchr 字符串正向查找字符
08 strrchr 字符串倒叙查找字符
09 strstr 字符串匹配
10 strtok 字符串切割

内存操作函数

序号 函数 功能
01 memset 内存设置,多用于内存清零
02 memcpy 内存拷贝连续空间
03 memcmp 内存空间比较

字符串和数值转换

序号 函数 功能
01 atoi 字符串转int类型
02 atol 字符串转long类型
03 atof 字符串转double类型

字符串格式化

  • sscanf和%s结合时,遇到空格、回车、'\0'结束取字符串
  • %[width]d 或 %[width]s 提取指定宽度
  • %*d或%*s 跳过数据
  • %*[width]d或%*[width]s 跳过指定宽度数据
  • 集合操作,只获取字符串,遇到不符合条件的,就不再继续匹配
    • %[a-z] 匹配a-z中的一个
    • %[aBc] 匹配a、B、c中的一个
    • %[^aFc] 匹配非a、F、c中的一个
    • %[^a-z] 匹配非a-z中的一个
序号 函数 功能
01 sprintf 将字符串输出到数组
02 fprintf 将字符串输出到文件
03 sscanf 从数组获取字符串
04 fscanf 从文件获取字符串

数学库

gcc -lm XXX.c

文件

以t的方式读取,以EOF作为文件末尾标识
以b的方式读取,以feof()作为文件末尾标识

序号 函数 功能
01 fopen 打开文件。返回 FILE*
02 fclose 关闭文件。传参 FILE*
03 fputc 写一个字符
04 fgetc 读一个字符
05 feof 判断是否为文件结尾。结尾返回非零
06 fputs 写一行。失败返回-1
07 fgets 读一行(包括'\n')。失败返回NULL
08 fwrite 写一块。返回写入块数=第三个参数
09 fread 读一块。返回值是真正读到的整数块
10 fprintf 按格式写入文件
11 fscanf 按格式读文件
12 rewind 复位文件流指针
13 ftell 测文件流指针距首部多少字节
14 fseek 定位位置指针
int fseek(FILE *stream, long offset, int whence);
whence:
    0:定位文件的开头
    1:定位文件的当前位置
    2:定位文件的尾部
offset:从定位位置偏移字节数 正数向右偏移 负数向左偏移