1 关键字
1.1 数据类型相关的关键字
char、short、int、long、float、double
struct、union、enum、signed、unsigned、void
在32位平台:
类型 | 占用字节数 | |
---|---|---|
char | 字符类型 | 1字节 |
short | 短整型 | 2字节 |
int | 整型 | 4字节 |
long | 长整型 | 4字节 |
float | 单精度浮点数 | 4字节 |
double | 双精度浮点数 | 8字节 |
struct | 结构体 | ~ |
union | 共用体 | ~ |
enum | 枚举 | ~ |
signed | 有符号数 | ~ |
unsigned | 无符号数 | ~ |
1.2 存储相关的关键字
register、static、const、auto、extern
关键字 | |
---|---|
register | 寄存器变量 |
static | 静态变量 |
const | 只读变量 |
auto | 自动变量 |
extern | 外部可用变量 |
1.2.1 const修饰变量为只读
const修饰变量为只读属性,本质为变量。
特点:只能初始化,不可以赋值。
#include <stdio.h>
int main(int argc, char **argv)
{
const int a = 10;
a = 20;
return 0;
}
$ gcc 14const.c
14const.c: In function ‘main’:
14const.c:8:7: error: assignment of read-only variable ‘a’
8 | a = 20;
|
1.2.2 register修饰寄存器变量
如果变量被频繁使用 会自动将变量存储在寄存器中。如果用户想人为将变量放入寄存器中,可以加register修饰。
目的:提高访问效率
#include <stdio.h>
int main(int argc, char **argv)
{
// 变量用register修饰,不管变量有没有放在寄存器中,都无法取(内存)地址
register int num = 0;
#
return 0;
}
$ gcc 15register.c
15register.c: In function ‘main’:
15register.c:8:5: error: address of register variable ‘num’ requested
8 | #
|
1.3 控制语句相关的关键字
if、else、break、continue、for、while、do、switch、case、goto、default
1.4 其他关键字
sizeof、typedef、volatile
sizeof:测量类型的大小
typedef:给类型取别名
volatile:防止编译器优化
1.4.1 volatile关键字
功能1:强制访问内存
功能2:防止编译器优化
1.4.2 sizeof测量类型的大小
#include <stdio.h>
int main(int argc, char **argv)
{
int num = 0;
printf("%lu ", sizeof(num));
printf("%lu ", sizeof(int));
char ch = 'a';
printf("%lu ", sizeof(char));
printf("%lu ", sizeof(ch));
printf("%lu\n", sizeof('a')); // 整型
return 0;
}
$ ./a.out
4 4 1 1 4
1.4.3 typedef给已有类型重新取个别名
不可以重建新类型。
将长且复杂的类型名取一个短小的名称。
typedef作用的步骤:
- 先用已有的类型,定义一个普通变量。
- 用别名代替变量名。
- 在整个表达式最前面加上typedef。
步骤1:int num;
步骤2:int INT;
步骤3:typedef int INT;
INT num; == int num;
2 数据类型
2.1 变量
变量名命名规则:变量由数字、字母、下划线组成,且不能以数字开头,不可以为关键字。
int auto; // 错误,变量名不可以为关键字
int 3num_; // 错误,变量名不可以数字开头
变量定义时请务必初始化,防止不必要报错。
int a = 10; // 变量初始化
int b;
b = 20; // 不是初始化,是赋值
变量的定义、声明和使用
- 变量的定义为变量开辟空间
- 变量的声明是说明变量的名称类型
- 变量的使用是对变量的读写
变量获取键盘输入scanf
#include <stdio.h>
int scanf(const char *format, ...);
format 获取数据的格式
示例1:
#include <stdio.h>
int main(int argc, char **argv)
{
int num = 0;
printf("请输入一个int类型数据: ");
scanf("%d", &num);
printf("num = %d\n", num);
return 0;
}
$ ./a.out
请输入一个int类型数据: 100
num = 100
%d和scanf结合表只能提取数值,扫描到非数值立刻退出!!!但是剩余值还留在存储区内部,需清除缓冲区。
示例2:键盘输入两个数并且求和
#include <stdio.h>
int main(int argc, char **argv)
{
int num1 = 0;
int num2 = 0;
printf("请输入两个数字:");
scanf("%d:%d", &num1, &num2);
printf("%d + %d = %d\n", num1, num2, num1 + num2);
return 0;
}
$ ./a.out
请输入两个数字:10:40
10 + 40 = 50
2.2 字符类型
字符类型数据在计算机中以ASCII码值存储。
示例:
#include <stdio.h>
int main(int argc, char **argv)
{
printf("%c\n", 'b');
printf("%d\n", 'b');
return 0;
}
$ ./a.out
b
98
单引号’'的作用:
- 'a’描述a为字符。
- 取字符的ASCII码值。
字符变量的初始化
char ch;
char ch = 'a';
char ch = '\0';
char ch = 0; // 数值0,与'\0'一样
char ch = '0'; // 字符0,对应ASCII码值48
示例1:获取字符并输出
#include <stdio.h>
int main(int argc, char **argv)
{
char ch = '\0';
printf("请输入一个字符:");
#if 0
scanf("%c", &ch); // 从缓冲区扫描一个字符
getchar(); // 清除缓冲区中的回车
#else
ch = getchar();
getchar();
#endif
printf("%c\n", ch);
printf("%d\n", ch);
return 0;
}
$ ./a.out
请输入一个字符:p
p
112
示例2:字符大小写切换
键盘输入一个字符,如果是大写 就转换成小写,如果是小写 就转换成大写。
#include <stdio.h>
int main(int argc, char **argv)
{
char ch = '\0';
printf("请输入一个字符:");
ch = getchar();
if (ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
else if (ch >= 'A' && ch <= 'Z')
{
ch += 32;
}
else
{
printf("输入错误\n");
return -1;
}
printf("%c\n", ch);
return 0;
}
$ ./a.out
请输入一个字符:Y
y
2.3 实型(浮点数)float,double
以f结尾的为float类型
不以f结尾的为double类型
#include <stdio.h>
int main(int argc, char **argv)
{
float f = 0.0f;
double d = 0.0;
printf("请输入两个浮点数:");
// 获取键盘输入
scanf("%f %lf", &f, &d);
// 输出
printf("%f %lf\n", f, d);
return 0;
}
$ ./a.out
请输入两个浮点数:3.4 6.6
3.400000 6.600000
2.4 有符号数和无符号数
类型 | 对应匹配符 |
---|---|
int | %d |
short | %hd |
long | %ld |
unsigned int | %u |
unsigned short | %hu |
unsigned long | %lu |
3 进制
C语言不能直接输出二进制,但是可以输出十进制、八进制和十六进制。
输出进制 | 输出格式匹配 |
---|---|
十进制 | %d |
八进制 | %o / %#o |
十六进制 | %x / %#x |
示例:验证输出进制数
#include <stdio.h>
int main(int argc ,char **argv)
{
int num = 89;
printf("%d \n", num); // 以十进制输出
printf("%o ", num); // 以八进制输出
printf("%#o \n", num); // 以八进制的格式输出
printf("%x ", num); // 以十六进制输出
printf("%#x \n", num); // 以十六进制的格式输出
return 0;
}
$ ./a.out
89
131 0131
59 0x59
进制的转换
4 原码、反码、补码、
- 计算机中存储的数据为补码。
- 反码为原码和补码之间的桥梁。
- 原码是数据的二进制形式。
补码的意义:
- 统一了0的编码。
+0补码:0000 0000
-0补码:0000 0000
- 将减法运算变为加法运算。
5 计算机对数据的输出
%d输出,对于不管是int类型存储还是unsigned int类型存储,一律看4字节的最高位:
- 如果4字节最高位为1,求补码输出,为负数。
- 如果4字节最高位为0,内存原码输出,为正数。
#include <stdio.h>
int main(int argc, char **argv)
{
int num1 = 0xf0000009;
unsigned int num2 = 0xf0000009;
int num3 = 0x00000009;
unsigned int num4 = 0x00000009;
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
printf("num3 = %d\n", num3);
printf("num4 = %d\n", num4);
return 0;
}
$ ./a.out
num1 = -268435447
num2 = -268435447
num3 = 9
num4 = 9
%d输出,对于char类型存储,看1字节最高位:
- 如果1字节最高位为1,求补码输出,为负数。
- 如果1字节最高位为0,内存原码输出,为正数。
对于unsigned char 类型存储,一律内存原码输出。
#include <stdio.h>
int main(int argc, char **argv)
{
char ch1 = 0xf9;
char ch2 = 0x09;
unsigned char ch3 = 0xf9;
unsigned char ch4 = 0x09;
printf("ch1 = %d\n", ch1);
printf("ch2 = %d\n", ch2);
printf("ch3 = %d\n", ch3);
printf("ch4 = %d\n", ch4);
return 0;
}
$ ./a.out
ch1 = -7
ch2 = 9
ch3 = 249
ch4 = 9
%#x输出,对于无论int还是unsigned int类型存储,内存原码输出。
%#x输出,对于char类型存储,看1字节最高位:
- 如果1字节最高位为1,补1至4字节输出。
- 如果1字节最高位为0,内存原码输出。(实际为补0至4字节)
%#x输出,对于unsigned char类型存储,内存原码输出。
#include <stdio.h>
int main(int argc, char **argv)
{
int num1 = 0xf0000009;
int num2 = 0x00000009;
unsigned int num3 = 0xf0000009;
unsigned int num4 = 0x00000009;
char ch1 = 0xf5;
char ch2 = 0x05;
unsigned char ch3 = 0xf5;
unsigned char ch4 = 0x05;
// 输出
printf("num1 = %#x\n", num1);
printf("num2 = %#x\n", num2);
printf("num3 = %#x\n", num3);
printf("num4 = %#x\n", num4);
printf("ch1 = %#x\n", ch1);
printf("ch2 = %#x\n", ch2);
printf("ch3 = %#x\n", ch3);
printf("ch4 = %#x\n", ch4);
return 0;
}
$ ./a.out
num1 = 0xf0000009
num2 = 0x9
num3 = 0xf0000009
num4 = 0x9
ch1 = 0xfffffff5
ch2 = 0x5
ch3 = 0xf5
ch4 = 0x5
%u输出,对于无论int还是unsigned int类型存储,一律内存原码输出。
%u输出,对于char类型存储,看1字节最高位:
- 如果1字节最高位为1,补1至4字节内存原码输出。
- 如果1字节最高位为0,内存原码输出。(实际为补0至4字节)
%u输出,对于unsigned char类型存储,内存原码输出。
#include <stdio.h>
int main(int argc, char **argv)
{
int num1 = 0xf0000009;
int num2 = 0x00000009;
unsigned int num3 = 0xf0000009;
unsigned int num4 = 0x00000009;
char ch1 = 0xf5;
char ch2 = 0x05;
unsigned char ch3 = 0xf5;
unsigned char ch4 = 0x05;
// 输出
printf("num1 = %u\n", num1);
printf("num2 = %u\n", num2);
printf("num3 = %u\n", num3);
printf("num4 = %u\n", num4);
printf("ch1 = %u\n", ch1);
printf("ch2 = %u\n", ch2);
printf("ch3 = %u\n", ch3);
printf("ch4 = %u\n", ch4);
return 0;
}
$ ./a.out
num1 = 4026531849
num2 = 9
num3 = 4026531849
num4 = 9
ch1 = 4294967285
ch2 = 5
ch3 = 245
ch4 = 5
6 转义字符
\和某些字符结合产生新的字符含义。
6.1 八进制转义
‘\ddd’:
- 每个d的范围必须是0~7。
- 3个d表示最多识别3位八进制数据。
以下字符描述正确的是 A
A:'\123'
B:'\18' 越界 2位
C:'\1234' 越界 2位
D:'\183' 越界 3位
尤其注意八进制转义需要考虑溢出, 最大为255, 最大的八进制转义为'\377'
char ch = '\0';
for (; ch < 1000; ch++); // 死循环
6.2 十六进制转义
‘\xhh’:
- 每个h的范围是09、af。
- 两个h表示最多识别2位十六进制。
7 类型转换(临时转换)
7.1 自动类型转换
例1:int与unsigned int转换成unsigned int
#include <stdio.h>
int main(int argc, char **argv)
{
int num1 = -10;
unsigned int num2 = 6;
if (num1 + num2 > 0) // unsigned int > 0
{
printf(">0\n");
}
else
{
printf("<0\n");
}
printf("%d\n", num1 + num2); // %d输出unsigned int类型只看最高位,为1,转换为补码输出
printf("%u\n", num1 + num2); // 内存原码输出
return 0;
}
$ ./a.out
>0
-4
4294967292
例2:int和double类型参加运算,int将转换为double类型
#include <stdio.h>
int main(int argc, char **argv)
{
int num1 = 0;
double num2 = 0.0;
printf("%lu\n", sizeof(num1 + num2)); // = sizeof(double) = 8
return 0;
}
$ ./a.out
8
例3:char和short类型只要参加运算,都会将自己转换成int类型
#include <stdio.h>
int main(int argc, char **argv)
{
short num1 = 0;
char ch = 'a';
printf("%lu\n", sizeof(num1 + num1)); // 4
printf("%lu\n", sizeof(num1 + ch)); // 4
printf("%lu\n", sizeof(ch + ch)); // 4
return 0;
}
$ ./a.out
4
4
4
7.2 强制类型转换
(类型说明符)(表达式)
(int)p + 1
(int)(p + 1)
8 运算符
如果运算符需要一个运算对象,就叫单目运算符。
如果运算符需要两个运算对象,就叫双目运算符。
如果运算符需要三个运算对象,就叫三目运算符。
如果运算符需要多个运算对象,就叫多目运算符。
8.1 算数运算符
算数运算符 | 符号 |
---|---|
加 | + |
减 | - |
乘 | * |
除 | / |
取余 | % |
加等于 | += |
减等于 | -= |
乘等于 | *= |
除等于 | /= |
取余等于 | %= |
#include <stdio.h>
int main(int argc, char **argv)
{
printf("%d\n", 9 / 4);
printf("%lf\n", 9 / 4.0);
return 0;
}
$ ./a.out
2
2.250000
%取余运算符,不可以用于浮点数(实型)。
例1:如果random()函数产生一个随机数>0,请使用rand()产生60~100的随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char **argv)
{
// 设置随机数种子
srandom(time(NULL));
for (int i = 0; i < 5; i++)
{
// 生成60~100之间的随机数
printf("%d ", random() % 41 + 60);
}
// 换行
printf("\n");
return 0;
}
$ ./a.out
83 97 79 100 67
$ ./a.out
62 99 74 64 88
$ ./a.out
77 66 67 83 74
$ ./a.out
74 90 75 91 68
$ ./a.out
60 75 73 84 77
例2:如果random()函数产生一个随机数>0,请使用rand()产生’a’~'z’的随机字母
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char **argv)
{
// 设置随机数种子
srandom(time(NULL));
for (int i = 0; i < 5; i++)
{
printf("%c ", random() % 26 + 'a');
}
printf("\n");
return 0;
}
$ ./a.out
z p m u g
$ ./a.out
u g y e h
$ ./a.out
x s y h b
$ ./a.out
p a m l m
$ ./a.out
s t l w b
8.2 复合运算符
注意:一定要将等号的右边看做一个整体
案例1:
#include <stdio.h>
int main(int argc, char **argv)
{
int a = 3;
a *= 4 + 5;
printf("a = %d\n", a);
return 0;
}
$ ./a.out
a = 27
案例2:
8.3 关系运算符
关系运算符 | 符号 |
---|---|
大于 | > |
小于 | < |
等于 | == |
小于等于 | <= |
大于等于 | >= |
不等于 | != |
![]() |
8.4 逻辑运算符
逻辑运算符 | 符号 |
---|---|
与 | && |
或 | || |
非 | ! |
逻辑与的短路特性:A && B
,如果判断A为假,编译器不会判断B的结果。
逻辑或的短路特性:A || B
,如果判断A为真,编译器不会判断B的结果。
在C语言中,除了0为假,其他都为真。
8.5 位运算(二进制位运算)
8.5.1 & 按位与
语法:只有全为1,才为1。
特点:和1相与,保持不变;和0相与,全为0。
场景:将指定位清零。
例:data为1字节,将data的第3,4位清零,其他位保持不变
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned char data = 0xff;
printf("data = %#x\n", data);
data &= ~(0x1 << 3 | 0x1 << 4);
printf("data = %#x\n", data);
return 0;
}
$ ./a.out
data = 0xff
data = 0xe7
8.5.2 | 按位或
语法:有1为1,全0为0。
特点:和0相或,保持不变;和1相或,全为1。
场景:将指定位置1。
例:data为1字节,将data的第3,4位置1,其他位保持不变
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned char data = 0x00;
printf("data = %#x\n", data);
data |= (0x1 << 3 | 0x1 << 4);
printf("data = %#x\n", data);
return 0;
}
$ ./a.out
data = 0
data = 0x18
8.5.3 ~ 按位取反
0变1,1变0。
8.5.4 ^ 按位异或
语法:相同为0,不同为1。
特点:和0异或,保持不变;和1异或,翻转。
场景:将指定位翻转。
8.6 左移和右移
左移<<:左边丢弃,右边补0
右移>>:右边丢弃,左边补0/1
算数右移和逻辑右移为编译器决定,用户无法确定!!!