1
2
|
scoop install mingw #windows
sudo apt install gcc #ubuntu/wsl
|
| 关键字 |
名称 |
大小(64位系统) |
值域 |
| int |
整型 |
4字节 |
-2^31, 2^31-1 |
| float |
浮点型 |
4字节 |
… |
| long int |
长整型 |
8字节 |
-2^63, 2^63-1 |
| long long int |
长长整型 |
8字节 |
|
| short |
短整型 |
2字节 |
-2^15, 2^15-1 |
| bool |
布尔类型 |
1字节 |
0, 1 |
| unsiged int |
无符号整型 |
4字节 |
0, 2^32-1 |
- 整型数据在内存中存储都是以补码的方式存放
- 数据在运算的时候是以补码的方式运算
- 整数反码补码都是一样的
- 补码运算的时候,符号位也参与运算
- 运算结果为补码,printf函数打印时,补码还原为原码
- mian函数外部只用来定义和声明全局变量
- 全局变量在定义时没有赋值,默认为0
- 赋值–实际上就是将数据存储变量所在的内存空间
- =赋值符号的左边称之为左值,指的是变量的内存空间
- =赋值符号的右边称之为右值,指的是变量的值
1
2
3
4
5
6
|
#include<stdio.h>
int main() {
printf("hello, world");
while(1);
return 0;
}
|
当我们程序运行的时候,将数据输出到屏幕终端,首先数据会先放入一个缓冲区中,然后当达到一定条件时,数据才输出到终端
满足条件
- 遇到换行符时,系统会将这个程序的输出缓冲区的数据冲刷到屏幕终端上
- fflush(stdout)刷新缓冲区
- 缓冲区满
占位符
| 占位符 |
输出类型 |
扩展 |
| %d |
整型 |
%-{x}d 左对齐x个字符 |
| %f |
浮点型 |
%.{x}f 表示保留小数点后x位 |
| %e |
科学计数法 |
以科学计数法的方式输出浮点数 |
| %o |
八进制 |
%#o 按格式输出 |
| %x |
十六进制 |
%#x 按格式输出 |
| %c |
字符 |
|
| %s |
字符串 |
|
1
2
3
4
5
|
#include <stdio.h>
int main() {
printf("\033[1;31m Hello, World! \033[0m\n"); // 红色文本
return 0;
}
|
- 原型:int scanf(const char *format,…)
- 作用:从键盘上获取数据,存储到变量的内存空间中。
- 参数:const char*format是格式控制字符串,它指定了输入数据的格式。
- 返回值:返回的是整型数据,代表获取成功的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include<stdio.h>
int main() {
int age;
float score;
int ret;
ret = scanf("%d %f", &age, &score);
// 获取失败,重新处理
while(ret < 2) {
printf("获取失败,请重新输入\n");
ret = scanf("%d%f", &age, &score);
}
printf("ret:%d age:%d score:%f\n", ret, age, score);
}
|
程序进入死循环,在while循环中scanf函数没有等待输入
- scanf函数没有正确读取,不会从输入缓冲区拿走数据
- 在第二次输入前清空缓冲区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include<stdio.h>
int main() {
int age;
float score;
int ret;
ret = scanf("%d %f", &age, &score);
// printf("%d", ret);
// 获取失败,重新处理
while(ret < 2) {
// 清空输入缓冲区
while (getchar() != '\n');
printf("获取失败,请重新输入\n");
ret = scanf("%d%f", &age, &score);
}
printf("ret:%d age:%d score:%f\n", ret, age, score);
}
|
%c会把空格和换行符在内的字符读入,
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include<stdio.h>
int main() {
int age;
char sex;
printf("请输入年龄:");
scanf("%d",&age);
while(getchar() != '\n');
printf("请输入性别:");
scanf(" %c", &sex);
printf("age:%d sex:%c", age, sex);
return 0;
}
|
- 除法运算时,除数不能为0
- 整型/整型 结果只能是整型,小数部分直接舍弃,即向下取整
- 若参与运算的值有一个是浮点数,运算过程中把所有数转化为浮点数,运算结果也是浮点数
- 自增属于单目运算符,用于一个数的左端或者右端
- 如果++在后,先参与运算,再自增
- 如果++在前,先自增,再参与运算
1
2
3
4
5
6
7
8
9
10
11
12
|
#include<stdio.h>
int main() {
int x = 2;
int y = 10 + x++;
printf("x=%d, y=%d\n", x, y);
// x=3, y=12
int z = 10 + (++x);
printf("x=%d, z=%d\n", x, z);
// x=4, z=14
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include<stdio.h>
int main() {
int i = 3;
int ret = i++ + i++; // 3 + 4
printf("i=%d, ret=%d\n", i, ret);
// i=5, ret=7
i = 3;
ret = i++ + (++i); // 3 + 5
printf("i=%d, ret=%d\n", i, ret);
// i=5, ret=8
return 0;
}
|
- 由关系运算符组成的表达式称之为关系表达式
- 关系表达式的结果只有两种,要么关系成立为
1,不成立为0
1
2
3
4
5
6
7
8
9
|
#include<stdio.h>
int main() {
int a = 10;
printf("%d\n", 0<a<2); // 1
int b = a == 10;
printf("%d\n", b); // 1
return 0;
}
|
由逻辑运算符组成的表达式称之为逻辑表达式,由逻辑运算符组成的表达式称之为逻辑表达式,逻辑表达式的结果是布尔值,要么为真(非0为真),要么为假
- 逻辑表达式中存在惰性运算(短路特性),如果前面的表达式已经能表达出明确的结果了,那么后面就不会算了。
位运算指将一个数据进行bit位运算,也就是说,要将这个数据转成二进制(补码),然后再进行运算位操作。
- 按位或|:至少有一位为1,结果就为1
- 按位与&:两个都为1,结果才为1
- 按位取反~:把每个bit位的0变为1,1变为0
- 按位异或^:两个比特位不同为1,相同为0
- 按位左移«:除符号位,所有的bit位左移x位
- 按位右移»:除符号位,所有的bit位右移x位
移位运算中如果出现如下情况,则其行为未定义:
- 右操作数(即移位数)为负值;
- 右操作数大于等于左操作数的位数;
例如,对于 int 类型的变量 a , a<<-1 和 a<<32 都是未定义的。
对于左移操作,需要确保移位后的结果能被原数的类型容纳,否则行为也是未定义的。对一个负数执行左移操作也未定义。
对于右移操作,右侧多余的位将会被舍弃,而左侧较为复杂:对于无符号数,会在左侧补
0
;而对于有符号数,则会用最高位的数(其实就是符号位,非负数为0 ,负数为 1))补齐
1
2
3
4
5
6
|
int mulPowerOfTwo(int n, int m) { // 计算 n*(2^m)
return n << m;
}
int divPowerOfTwo(int n, int m) { // 计算 n/(2^m)
return n >> m;
}
|
1
2
3
4
5
|
void swap(int &a, int &b) {
a ^= b;
b ^= a;
a ^= b;
}
|
1
2
3
4
|
// 获取 a 的第 b 位,最低位编号为 0
int getBit(int a, int b) {
return (a >> b) & 1;
}
|
1
2
3
4
|
// 将 a 的第 b 位设置为 0 ,最低位编号为 0
int unsetBit(int a, int b) {
return a & ~(1 << b);
}
|
1
2
3
4
|
// 将 a 的第 b 位设置为 1 ,最低位编号为 0
int setBit(int a, int b) {
return a | (1 << b);
}
|
1
2
3
4
|
// 将 a 的第 b 位取反 ,最低位编号为 0
int flapBit(int a, int b) {
return a ^ (1 << b);
}
|
1
2
3
|
int getLastOne(int a) {
return a & (a-1);
}
|
1
2
3
|
int removeLastOne(int a) {
return a ^ (a & -a);
}
|
1
2
3
4
5
6
7
|
if ("表达式") {
//code
} else if {
//code
} else {
//code
}
|
- 只要表达式的值为真(非0),执行{ }复合语句 .
- “表达式”:任意的C语言合法的表达式都可以
1
2
3
4
5
6
7
8
9
10
11
12
|
switch("表达式") {
case 1:
//code
break;
case 2:
//code
break;
...
default:
//code
break
}
|
- “表达式"的值必须为整数值(整型,字符型,枚举)
- “常量表达式"的值必须为整数值,且每个case后面的常量表达式的值必须不相同。
- 当表达式的值与某一个case后面的常量表达式的值相同时,就会打开"开关”,执行冒号后面的语句,直到有break语句就提前跳出switch,否则就轮到下一个case,且不会比较常量表达式的值,就直接执行冒号后面的语句(因为开关已经打开了)。当所有的语句都执行完了,就结束了switch。
- break;提前跳出switch语句,常用的用法是每个case语句后都有一个break语句。
- 当所有case后面的常量表达式都不匹配时,就会执行default后面的语句,且打开"开关”。
注意:
- break在这里是退出switch的关键,如果在case当中的执行语句中没有遇到break,它将会一直执行下去,直到遇到break或者到底了
- default这个可加可不加
- default放在哪里都不会影响switch的正常使用
- “判断值”只能是整型数据或者是枚举
- goto语句与if构成的循环
- while循环
- do~while循环
- for循环
1
2
3
|
语句标号:
// code
goto 语句标号; //此时程序将会直接跳转到语句标号的地方开始执行
|
作用:
- 与if语句向上跳转构成循环结构
- 向后跳转可以跳出多重循环体等语句
注意:
- goto本身没有任何问题,但是goto语句是无条件跳转语句,应用太灵活,会使程序的可读性变差,应限制使用。
- goto语句只能够在函数内进行跳转,不可以跨越函数跳转。
- goto语句一般用于代码出现异常时,直接goto跳转到异常处理的代码处。比如说多层循环嵌套执行时出现异常,则直接跳出。
1
2
3
4
|
while(表达式)//表达式的值为真(非0)时,进入循环体;为假时,跳出循环体
{
循环体
}
|
do while循环的特点:无论判定条件是否为真,至少会执行一遍循环体
1
2
3
|
do{
循环体;
}while(表达式);
|
1
2
3
4
5
6
7
|
for( 初始化语句表达式1; 判断语句表达式2; 循环操作语句表达式3) {
循环体
}
for(int i=0; i<5; i++) {
printf("%d\n",i);
}
|
- for循环中 的表达式123都可以为空,表达式2为空表示判断条件永远为真。while的表达式不能为空。
- while循环与for循环的区别:一般来说,while更注重循环条件,for更注重循环次数。 当然它们是可以相互替换的。
1
2
3
4
5
6
7
8
|
int i,j,a=0;
for(i=0;i<5;i++){
//外循环
for(j=0;j<4;j++) {
//内循环
printf("%d %d %d\n",i,j,a++);
}
}
|
- break关键字只能用于循环(while for do…while)和switch语句中,表示结束循环或者跳出switch语句
- break关键字只能跳出最近一层的循环,也就是说,如果有多重循环,只能跳出最靠近break语句那层循环
- break关键字 不能 单独用于 if语句中,除非if语句外面包含了循环
- continue关键字只能用于循环体中(while do…while for),用于提前结束当前循环体后,又从最近一层循环体开始执行
- continue关键字不能 单独用于 if语句或者switch 语句中,除非外面包含了循环
- 宏名:宏定义的标识符,一般用大写字母表示
- 宏体:宏定义的字符串,可以是常量、表达式、函数调用等
- 宏定义的作用:在预处理阶段,将所有的宏名替换成宏体,然后再进行编译
宏定义的注意事项:
- 宏定义没有类型,宏体可以是任意类型的值
- 宏定义没有作用域,宏定义的宏名在全局范围内都有效
- 宏定义是简单的文本替换,不会进行类型检查,可能会导致错误
- 宏定义可以包含参数,参数可以是任意类型的值
- 宏定义的参数没有类型,宏体中的参数会被替换成宏体中的值
1
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
由于宏定义中的参数可以为任何值,任何符号,参数可能为多条语句,为了宏定义能达到预期的效果,宏定义中的参数需要用括号括起来,否则可能会导致错误。
使用宏定义可以达到正常语法无法达到的效果。
例如
1
2
3
4
5
6
|
#define Vec(T) \
struct {\
T *data; \
int size; \
int capacity; \
}
|
在这个例子中,使用宏定义可以实现任意类型的动态数组,注意:在宏定义包含多行的时候,用续行符\进行连接,表明它们为同一行
在linux内核中,还可以经常看到这样的写法, 例如linux内核链表中初始化头结点的宏定义
1
2
3
|
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
|
在宏定义中,使用do...while(0)包裹起来,表明这些语句为一个整体,可以防止宏定义被误用,如果没有使用,例如在if语句中采用这样的格式
1
2
|
if (condition)
INIT_LIST_HEAD(ptr);
|
那么无论条件成立,宏定义中第二条以及之后的语句都会执行,所以,如果你的宏定义中包含多条语句,那么最好使用do...while(0)包裹起来。
在C语言中,宏定义被大量使用,但是由于宏定义没有类型检查,无法打断点调试,降低代码可读性,所以,在编写宏定义的时候,要格外小心。如果你的逻辑可以使用C语言的语法正常实现,那么请尽量不要使用宏定义。