C语言学习笔记 - 1
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码值
计算机存的都是数值,只是输出时表现形式是字符而已
仅是开辟了一个字节的空间,存什么都可以
- 字符常量和字符串常量的区别仅为
单引号
和双引号
之分 - '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位
转义字符
普通转义:'\0' '\n' '\t' '\r'(回到行首) '\a'(发出警报)
高级转义:
- '\ddd' 每个d的范围必须是
0 ~ 7
,3个d表示最多识别3位八进制数据 - '\xhh' 每个h的范围必须是
0 ~ 7
a ~ f
,2个h表示最多识别2位十六进制数据,或许能识别3位,但是会溢出
数据类型转换
- 编译器自动完成,小空间自动往大空间转,目的保证
精度不丢失
。 char
与short
只要运算了,都自动转化为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
的操作
-
结构体变量中有指针成员才有可能出现浅拷贝和深拷贝的问题
结构体变量 自身空间 内容会完全拷贝。变量内若带有指针指向 其他空间 ,其他空间 不会被拷贝,此时两个结构体变量的指针指向同一个 其他空间- 可能会导致同一个空间被释放两次
- 在统计数据的时候,将结构体中的数据拷贝到总结构体时,指针指向的空间会被第二次取值覆盖。
-
结构体内存对齐
- 确定分配单位长度
- 确定每个成员的偏移量
- 总大小为分配单位的倍数
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)
- 确定分配单位长度
min(结构体中最大的基本类型长度, value) 为分配单位 - 确定每个成员的偏移量
位域
- 不能对位域取地址:系统为每个字节分配地址,一个字节只有一个地址编号,没有给每个字节分配单位
- 位域不能超过自身字节数
- 位域标准写法一定是无符号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 |
进制转化:数据本身不变,仅表现形式不同
-
十进制转二进制、八进制、十六进制
短除法 -
二进制、八进制、十六进制转十进制
位次幂 -
二进制转八进制、十六进制
每3位二进制 对应 1位八进制
每4位二进制 对应 1位十六进制 -
八进制、十六进制转二进制
每1位八进制 对应 3位二进制
每1位十六进制 对应 4位二进制 -
八进制与十六进制之间没有直接转化方法
通过二进制转化
???
问题1:为什么前面给定了oct后面的数据流也是八进制?
3. 原码反码补码
概念
原码:计算机对数据的二进制直接的表达形式
原码、反码、补码转化规则
- 无符号数:原码 == 反码 == 补码
- 有符号正数:原码 == 反码 == 补码
- 有符号负数:
- 反码 == 原码符号位不变,其他位按位取反
- 补码 == 反码 + 1
补码的意义
- 将减法运算变为加法运算
- 统一了
0
的编码1000 0000 = -0 0000 0000 = +0 +0和-0都是低电平,所以-0没有意义,被计算机当为-128
数据存储
- 计算机以补码形式存储数据
- 八进制、十六进制没有负数概念,以原码存储
-10
存入栈区的值是 0xFFFF FFF6
(10的补码),并且从反汇编中能看到赋值命令的立即数是 0xFFFF FFF6
数据读取
- 对
无符号变量、八进制、十六进制
取值,输出内存的原样
数据 - 对
有符号变量
取值,看内存的最高位是否为1
,如果为0
原样输出,如果为1
则对内存数据再取一次补码
unsigned int data = -10;
-10
由unsigned类型数据读出时,输出 FFFF FFF6
,强转为signed
类型时,输出-10
。那么它在内存中到底是什么呢?
4. 关键字
const、register、volatile、sizeof、typedef
1. const
- const 修饰变量
只读
- 不能通过变量名去改值,但是可以通过指针间接修改空间数据。
1. 以常量初始化const变量
const int data = 10;
- 如何去定位
开辟空间
的动作?
怎么去参考? - 会不会是立刻开辟了,不能让用而已
C++概念:系统 不会立即
给 data
开辟空间,而是将 10
放入 符号常量表
,一旦用户取 data
的地址,系统立刻给 data
开辟空间。
C与C++的const区别:const
变量存储在只读数据段,可以通过指针修改,而C++的const在编译阶段会将变量替换为常量,达到真正的不可修改。
- 声明一个const类型的局部变量,观察到
data
的存储位置在栈区,理论上栈区是可读可写的,因此我们使用指针能够修改此空间数据是没有疑议的,但是最后输出data
时,却并不是这个值。。。
运行结果:*p = 1000, data = 10
【自释义:存在两个data,一个在栈区,一个是变量名与值直接映射的,取地址使用的是栈区的,变量名用的是映射的】
【二次探究:只有栈区一个data,在编译阶段,就将const变量在代码段进行了替换,我们取地址赋值的是栈区的data,所以最终的结果不一样】
- 声明一个const类型的全局变量,观察到
a
的存储位置明显远离了b、c
,运行结果:报错(引发了异常:写入访问权限冲突)【自释义:该只读变量存储空间的页表是只读的】
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
- 为已有的类型取别名,不能创建新的类型,不会影响原有的类型
- 别名要全部大写,见名知意
- 使用方法:
- 用
已有的类型
定义一个变量 - 使用
别名
替换变量名 - 在表达式的最左边加上关键字
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" ---> 比较的是两个字符串的地址
- 用于数值的判断
逻辑运算符短路特性
&& 短路特性:遇到假即为假,不会判断下一组表达式
|| 短路特性:遇到真即为真,不会判断下一组表达式
! 非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
- 当位移长度大于变量宽度时,真正位移的位数先对宽度取余
三目运算符
表达式?值1:值2
表达式为真,取值1,为假取值2
运算符优先级(重要)
自增自减运算符
++i i++
作为独立语句,两者没有区别- 混合运算时,若
++ --
在右边,先使用,后运算- eg: j = i++; // j = i; i++;
- 混合运算时,若
++ --
在左边,先运算,后使用- eg: j = ++i; // i++; j = i;
逗号运算符(优先级最低)
int num = 0;
num = 10, 20, 30, 40;
cout << num << endl; // 10
num = (10, 20, 30, 40);
cout << num << endl; // 40
当逗号运算符遇到括号时,从左至右运算,到达最后一个表达式时,会按照优先级判断。
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:从定位位置偏移字节数 正数向右偏移 负数向左偏移
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效