1 变量和数据类型
1.1 常用基本数据类型占用空间
- char(字符型) : 1个字节
- int(整型) :4个字节
- float(单精度浮点型):4个字节
- double(双精度浮点型):8个字节
下表为详细的变量的数据类型及其他参数
1.2 基本类型书写
1.2.1 整数
- dec,默认为10进制 ,10 ,20
- oct,以0开头为8进制,045,021
- bin,以0b开头为2进制,0b11101101
- hex,以0x开头为16进制,0x21458adf
1.2.2 小数
- 3.14 双精度浮点数
- 0.1 双精度浮点数
- -2.18 负双精度浮点数
- 6.022e23 科学计数法表示的双精度浮点数,等于6.022 × 10^23
- 3.14f 单精度浮点数
1.2.3 字符型常量
- 'A' 字母A
- '1' 数字1
- '$' 特殊字符
- '\n' 换行符(ASCII码值为10)
2 常量
常量是固定值,在程序执行期间不会改变。
在 C 中,有两种简单的定义常量的方式:
2.1 #define
#define指令是C语言中用于宏定义的预处理指令,它可以在源代码被编译之前由预处理器处理。这种方式定义的常量通常称为宏常量。
#define PI 3.14159
#define MAX_VALUE 100
2.2 const
const关键字用于声明一个只读的变量,即常量。这种方式定义的常量具有特定的类型,并且在程序中占用内存空间。
const float pi = 3.14159;
const int max_value = 100;
指针与常量:
指向常量的指针(指针指向的值不能修改):
const int *ptr = &max_value;
常量指针(指针本身的值不能修改,但指向的值可以):
int value = 10;
int *const ptr = &value;
指向常量的常量指针(指针指向的值和指针本身的值都不能修改):
const int value = 10;
const int *const ptr = &value;
3 存储类说明符
C语言提供了几种存储类说明符,可以用来声明变量和函数
3.1 auto
存储位置:自动存储位置,通常是栈(Stack)。
生命周期:局部变量的生命周期通常是从它被创建的函数开始,到该函数结束时结束。
作用域:局部作用域,只能在声明它的块(如函数或循环)内可见。
示例:auto int var; (在现代C语言中,auto很少使用,因为它是局部变量的默认存储类)
3.2 register
存储位置:寄存器(如果可能的话)。如果无法将变量存储在寄存器中,则存储在自动存储位置。
生命周期:与自动变量相同。
作用域:与自动变量相同。
示例:register int count; (提示编译器尝试将变量存储在寄存器中以加快访问速度,但编译器可以忽略此提示)
3.3 static
存储位置:静态存储位置,通常是程序的全局数据区。
生命周期:整个程序的运行期间。
作用域:
对于局部静态变量:仅在声明它的块内可见。
对于外部静态变量或函数:在整个文件内可见(对于外部静态变量)或整个程序可见(对于静态函数)。
3.4 extern
存储位置:外部链接存储位置,通常是程序的全局数据区。
生命周期:整个程序的运行期间。
作用域:在整个程序内可见,但必须在其他地方有定义。
在C语言中,extern关键字通常用于在多个源文件之间共享变量或函数。
使用方法:
1.在头文件中声明:在头文件中使用extern关键字声明你想要共享的变量或函数。
// shared.h
extern int sharedVar;
extern void sharedFunction();
2.在源文件中定义:在一个源文件中定义这些变量或函数。不需要在这个定义中使用extern关键字。
// shared.c
int sharedVar = 10;
void sharedFunction() {
// 函数实现
}
3.在其他源文件中使用:在其他需要使用这些变量或函数的源文件中包含头文件。
// main.c
#include "shared.h"
int main() {
sharedVar = 20;
sharedFunction();
return 0;
}
3.5 typedef
存储类说明符:typedef不是存储类说明符,而是用于创建类型别名的关键字。
示例:typedef unsigned char BYTE; (创建了一个名为BYTE的新类型,它等同于unsigned char)
4 运算符
4.1 算数预算符
运算符 | 描述 | 示例 |
+ | 加法运算符 | a + b |
- | 减法运算符 | a - b |
* | 乘法运算符 | a * b |
/ | 除法运算符 | a / b |
% | 取模运算符(求余数) | a % b |
++ | 自增运算符(前缀或后缀) | ++a 或 a++ |
-- | 自减运算符(前缀或后缀) | --a 或 a-- |
自增自减运算符前缀或后缀的区别:
前缀形式(++a):
在表达式被求值之前,变量的值先增加1,表达式的返回值是变量增加后的新值。
后缀形式(a++):
在表达式被求值之后,变量的值再增加1,表达式的返回值是变量增加前的原始值。
4.2 关系运算符
关系运算符用于比较两个值,并返回一个布尔结果(真true或假false)。
C语言中的关系运算符包括以下几种:
== != > < >= <=
4.3 逻辑运算符
&&(逻辑与):
描述:如果两个操作数都为真(非零),则表达式的结果为真。
示例:a && b
||(逻辑或):
描述:如果至少有一个操作数为真,则表达式的结果为真。
示例:a || b
!(逻辑非):
描述:反转操作数的逻辑值,如果操作数为真,则结果为假;如果操作数为假,则结果为真。
示例:!a
4.4 位运算符
&(位与):
描述:对两个操作数的每一位进行与操作。只有两个操作数的对应位都是1时,结果的对应位才是1,其余为0。
示例:a & b,如果a是0b10101010,b是0b01010101,则结果是0b00000000。
|(位或):
描述:对两个操作数的每一位进行或操作。只要两个操作数的对应位中有一个是1,结果的对应位就是1,其余为0。
示例:a | b,如果a是0b10101010,b是0b01010101,则结果是0b11111111。
^(位异或):
描述:对两个操作数的每一位进行异或操作。只有两个操作数的对应位不同时,结果的对应位才是1,其余为0。
示例:a ^ b,如果a是0b10101010,b是0b01010101,则结果是0b11111111。
~(位非):
描述:对操作数的每一位进行非操作。将操作数的每一位取反,即0变成1,1变成0。
示例:~a,如果a是0b10101010,则结果是0b01010101。
<<(左移):
描述:将操作数的所有位向左移动指定的位数。左移操作相当于乘以2的幂次方。
示例:a << b,如果a是0b10101010,b是2,则结果是0b01010100。
>>(右移):
描述:将操作数的所有位向右移动指定的位数。对于无符号整数,右移操作相当于除以2的幂次方。对于有符号整数,右移操作可能保持符号位不变(算术右移),或者在最高位补0(逻辑右移)。
示例:a >> b,如果a是0b10101010,b是2,则结果是0b01010101。
位运算符在C语言中非常强大,它们可以用于各种任务,例如:
4.4.1 设置特定的位
要设置一个特定的位,我们可以使用按位或运算(|)。例如,假设我们有一个整数 n,我们想要设置它的第 k 位,我们可以这样做:
n |= (1 << k);
这里的 << 是左移运算符,它将数字1向左移动 k 位。这样,1 << k 的结果在二进制表示中只有一个位为1,正好在第 k 位上。然后我们用按位或运算符 | 将 n 和 1 << k 进行运算,从而将 n 的第 k 位设置为1,而不改变其他位。
4.4.2 清除特定的位
要清除一个特定的位,我们可以使用按位与运算(&)和一个掩码。例如,如果我们想要清除整数 n 的第 k 位,我们可以这样做:
n &= ~(1 << k);
在这个操作中,~(1 << k) 会创建一个在二进制表示中除了第 k 位为0,其他位都为1的掩码。然后我们用按位与运算符 & 将 n 和这个掩码进行运算,从而只清除 n 的第 k 位,而不改变其他位。
4.4.3 切换特定的位
要切换一个特定的位,即如果该位是0则设置为1,如果是1则设置为0,我们可以使用按位异或运算(^)。例如,如果我们想要切换整数 n 的第 k 位,我们可以这样做:
n ^= (1 << k);
5 枚举
枚举(Enumeration)是一种由用户定义的数据类型,它允许将一组整型常量组织在一起,以便于管理和使用。
5.1 枚举的定义
枚举类型的定义使用enum关键字,后面跟着枚举类型的名称和一对花括号,花括号内包含枚举的所有成员。每个成员后面都跟着一个可选的等号和整数值。如果未明确指定值,则成员的值将自动从0开始递增。
enum Day {
MONDAY, // 默认值为0
TUESDAY, // 默认值为1
WEDNESDAY, // 默认值为2
THURSDAY, // 默认值为3
FRIDAY, // 默认值为4
SATURDAY, // 默认值为5
SUNDAY // 默认值为6
};
枚举成员的值可以是任何整数类型,包括负数。也可以为枚举成员显式指定值:
enum Month {
JAN = 1, // JAN的值为1
FEB, // FEB的值为2(自动递增)
MAR = 5, // MAR的值为5
APR // APR的值为6(自动递增)
};
5.2 枚举的使用
定义了枚举类型之后,就可以像使用其他数据类型一样声明枚举类型的变量,并给它们赋值。枚举类型的变量只能取枚举定义中列出的值。
enum Day workday;
workday = WEDNESDAY; // workday被赋值为WEDNESDAY,其对应的整数值为2
枚举也可以直接使用
#include <stdio.h>
#include <stdlib.h>
enum {
Q,W,E=4,R
};
int main()
{
printf("枚举值QWER分别是: %d , %d , %d , %d",Q,W,E,R);
return 0;
}
//输出:
//枚举值QWER分别是: 0 , 1 , 4 , 5
6 指针
6.1 指针的概念
指针是一个变量,其值为另一个变量的地址。在C语言中,每个变量都有一个内存地址,而指针变量存储了这个地址。通过指针,我们可以直接访问和操作内存中的数据,从而提高程序的效率和灵活性。
6.2 指针的类型
C语言中的指针有多种类型,包括整型指针、字符型指针、浮点型指针等。指针的类型决定了指针所指向的数据类型,以及指针的运算规则。例如,整型指针指向整型数据,字符型指针指向字符型数据。
6.3 指针的运算
指针的赋值是将一个变量的地址赋给指针变量,例如:
int a;
int *p;
p = &a; // 将变量a的地址赋给指针p
指定读写一个特定内存
unsigned int *p = (unsigned int *)0x80000000;
unsigned int value = *p; // 读取内存地址0x80000000的内容
*p = 0x12345678; // 向内存地址0x80000000写入值0x12345678
指针的加减运算是指针加上或减去一个整数,例如:
int a[10];
int *p = a; // 指针p指向数组a的第一个元素
p += 3; // 指针p向后移动3个元素,指向数组a的第四个元素
6.4 指针与数组
在C语言中,数组名是一个指向数组首元素的指针。因此,我们可以通过指针来访问和操作数组。例如,我们可以使用指针遍历数组:
int a[10];
int *p = a;
for (int i = 0; i < 10; i++) {
printf("%d ", *p); // 输出数组元素的值
p++; // 指针p向后移动一个元素
}
6.5 指针与函数
在C语言中,指针可以作为函数的参数和返回值。通过指针参数,函数可以修改实参的值;通过指针返回值,函数可以返回指向复杂数据结构的指针。
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int *find_max(int *a, int n) {
int *max = a;
for (int i = 1; i < n; i++) {
if (*(a + i) > *max) {
max = a + i;
}
}
return max;
}
7 结构体
7.1 结构体的定义
结构体的定义通常使用struct关键字,后跟结构体的名称和花括号内的成员列表。成员列表由一个或多个成员声明组成,每个成员声明指定了成员的名称和数据类型。
struct Student {
char name[50];
int age;
float score;
};
7.2 结构体变量的声明
一旦定义了结构体类型,就可以声明该类型的变量。声明结构体变量时,可以使用struct关键字和结构体名称,也可以直接指定变量名。
struct Student stu1; // 使用 struct 关键字和结构体名称
struct Student stu2;
或者,可以先省略掉结构体类型定义中的结构体标签,直接声明变量:
struct {
char name[50];
int age;
float score;
} stu1, stu2;
7.3 访问结构体成员
结构体成员可以通过点操作符(.)来访问。点操作符用于指定结构体变量的成员名称。
stu1.age = 20;
strcpy(stu1.name, "张三"); // strcpy 函数用于字符串复制
stu1.score = 92.5;
7.4 结构体指针
结构体指针指向结构体变量的地址。可以使用箭头操作符(->)通过指针访问结构体的成员。
struct Student *ptr = &stu1;
ptr->age = 21; // 等价于 (*ptr).age = 21;
8 头文件
8.1 头文件的类型
- 系统头文件:这些头文件通常由C语言实现或第三方库提供,包含了标准库函数的声明和宏定义。例如,stdio.h包含了标准输入输出库的声明,stdlib.h包含了标准库函数的声明。
- 用户自定义头文件:这些头文件由程序员编写,用于提供特定项目的接口。例如,一个项目可能会有一个config.h文件,其中包含了项目配置相关的宏定义。
8.2 头文件的包含
在C源文件中,可以使用#include预处理器指令来包含头文件。#include指令分为两种形式:
- 本地包含:使用双引号"包围的头文件路径。这种形式用于包含当前项目中的头文件。
#include "myheader.h"
- 系统包含:使用尖括号<和>包围的头文件名称。这种形式用于包含系统头文件。
#include <stdio.h>
8.3 头文件的内容
头文件通常包含以下内容:
宏定义:使用#define预处理器指令定义的符号常量。
#define MAX_VALUE 100
类型定义:使用typedef关键字定义的新类型。
typedef unsigned int UINT;
函数原型:函数的声明,告诉编译器函数的名称、返回类型和参数类型。
int add(int a, int b);
变量声明:声明外部变量,使得其他源文件可以使用这些变量。
extern int global_variable;
结构体和联合体定义:定义结构体和联合体类型。
struct Point {
int x;
int y;
};
内联函数:在头文件中定义的内联函数,可以在多个源文件中共享。
inline int min(int a, int b) { return a < b ? a : b; }
8.4 头文件的注意事项
防止重复包含:为了避免头文件被多次包含,可以使用#ifndef、#define和#endif预处理器指令来定义一个包含保护符。
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容
#endif // MYHEADER_H
使用标准的头文件包含方式:对于标准库头文件,应该使用尖括号< >,对于用户自定义头文件,应该使用双引号" "。
避免在头文件中定义变量:在头文件中定义变量会导致每个包含该头文件的源文件都有一个变量的副本,这通常不是预期的行为。应该只在头文件中声明变量,并在一个源文件中定义它们,然后通过extern关键字在其他源文件中引用。