C 基础
C
c是一种介于高级语言与低级语言之间的语言,将原文最终编译为机器码执行的。
- 编译工具 MinGW-W64 GCC
- 配置库环境 vscode 设置
"C_Cpp.updateChannel": "Insiders"
编译
#为c语言之中的编译符号
gcc为基本编译工具
-
预编译
预编译是指在编译之前对原文所做的预处理,生成
tmp.i文件。
- 文件预编译:引入
include<exp.h>(系统库),include"exp.h"(自定义库) 等头文件。- 条件预编译:
#if根据条件执行特定编译。- 常量预编译:
#FLAG替换预编译常量。- 删除代码注释
- 编译
- 将原文编译为汇编语言,生成
tmp.s文件。- 将原文编译为机器码,生成
tmp.o文件。- 将原文编译链接生成最终可执行文件,
tmp.exe文件。
# 将tmp.c编译为可执行文件tmp.exe windows平台默认为.exe文件
gcc ./tmp.c -o tmp.exe
# 将tmp.c预编译为tmp.i
gcc -E ./tmp.c -o tmp.i
# 将tmp.c预编译并重定向到tmp.txt
gcc -E ./tmp.c > tmp.txt
# 将tmp.c编译为汇编
gcc -S ./tmp.c
gcc -S ./tmp.c -o tmp.s
# 将tmp.c编译为obj文件
gcc -c ./tmp.c
gcc -c ./tmp.c -o tmp.o
- gcc 工具解析
| 指令 | 结果 | 文件 | 备注 |
|---|---|---|---|
| -E | 生成预编译文件 | tmp.i |
手动指定才可生成文件,>,-o |
| -S | 生成汇编文件 | tmp.s |
自动生成文件 |
| -c | 生成机器码文件 | tmp.o |
自动生成文件 |
| -o | 指定生成文件 | tmp.exe |
指定生成文件 |
| null | 生成可执行文件 | tmp.exe |
自动生成文件 |
变量
c语言的变量皆为强类型限定,且所有变量只要定义完成,其内存空间便已申请完成。
boolean
所有数值型变量(基础类型变量)以及NUll,可安全作为boolean。
完成空间申请的指针为true,未完成空间申请的指针会出 error。
| boolean | 标识范围 |
|---|---|
| true | 非0数值,成功定义的任意级指针 |
| false | 0,NULL |
变量的作用范围以及读写范围
c语言是一种块级代码,某一块内部变量外部是无法使用的,函数,以及循环体头部变量在外部是无法使用的。
| 关键词 | 名称 | 作用域 | 备注 |
|---|---|---|---|
#define |
宏变量 | 文件内部 | #define ~ #undef 或至文件结尾 |
auto |
块变量 | 当前快结构,或某些循环体头部 | 变量默认都是 auto |
register |
寄存器变量 | 定义与当前cpu寄存器同宽的值 | 运行时存在位宽限制 |
static |
静态变量 | 全局变量 | 读写皆可生效 |
extern |
引用变量 | 本文件生效 | 引用全局变量,或函数至本文件 |
const |
只读变量 | 变量可读不可修改 | 特殊修饰词,某些函数中的参数限定词 |
变量类型
- 宏变量(常量)
#define#undef定义撤销宏变量,此类型变量会在预编译时,替换或终止替换指定变量。
#define MAX_SIZE 1000
//......
#undef MAX_SIZE
- 基本数据类型
| 类型 | 符号 | 字节 | 备注 |
|---|---|---|---|
| char | yes | 1 | 由读取方式决定类型(small int/char) |
| short int | yes | 2 | 简写 short |
| int | yes | 4 | 最常用的整型 |
| long int | yes | 8 | 简写 long |
| float | no | 4 | 常用浮点数 |
| double | no | 8 | 大浮点 |
-
结构体 struct
结构体数据用于各类自定义数据结构。
typedef ... Example{...}example;
// 全名才可使用
struct Example exp;
union Example exp;
enum Example exp;
// 别名使用
example exp;
结构体定义之时,以所有部分内存的最小公倍数为基本单位(字节为基础单位),对结构体内部进行内存分配,且空间大小从上至下递增而非其他,其中多出的部分内存会被填充无法正常使用。即定义数据结构时,基本数据类型尽量不要变,且内存大小从上至下递增。
- typedef定义
1. 对变量类型赋予别名
2. 定义结构体类型- 结构体使用
1. 当为普通方式访问时用.
2. 当为指针方式访问时用->
typedef unsigned char byte;
typedef struct List{
byte* base;
byte* top;
byte* size;
}*list;
void main(){
int x = 10;
printf("输出:%d",x);
scanf("%d",&x);
//......
byte y = list->base;
byte z = (*list).base;
}
-
枚举类型 enum
常用于映射某些有限分类值,如星期几,几月,季节,性别...
一种映射模式,指定某些变量代表某些值,这些变量会变为常量。
typedef enum Season{
spring = 1, summer, autumn, winter
}season;
typedef enum Season{
spring = 1,
summer = 2,
autumn = 3,
winter = 4
}season_cmp;
void main(){
season sea = spring;
printf("The season's number is %d",sea);
printf("The season's number is %d",spring);
}
-
位域结构体
常用于高频次二项分布运算 0~1。
基本相似于结构体,不同之处在于位域结构体仅支持(unsigned int / int)的数据类型,以"位"为基本单位定义数据结构,数据默认存在为0,但存在BUG,不赋值时慎用。其内部是将int数值拆分作为一个整体运算的。
// 内存以 int 为单位,x,y不能超过位上限
typedef struct Flag{
unsigned int x:1; //0~1
unsigned int y:2; //0~3
}flag;
-
共用体 union
常用于高速缓存数据,或短时间记录某些值。
基本结构相似于结构体,唯一不同的是,其本省内存占用量就是最大数据的内存占用量,即同一时间里,一般仅为最近一被改写的数据是有效的,其他数据由于内存结构被覆盖会导致数据失效。
// 同一时间段仅有一个值有效,且内存为 4
typedef union Record{
unsigned int x;
unsigned int y;
unsigned int z;
}record;
指针
指针,包括指针空间本身以及所保存的一个地址,这个地址指向一个存储空间。
| 符号 | 普通变量 | 指针变量 | 数组变量 |
|---|---|---|---|
&p |
原始变量指针化 | 指针变量指针化(多级指针) | |
(int)&p |
存储空间地址(int) | 指针空间地址(int) | 存储空间头地址(int) |
(int)p |
强转int类型 | 存储空间地址(int) | 存储空间头地址(int) |
*将任意变量指针化,定义时使用。&对任意变量取地址,运算时使用。
| 类型 | 定义 | 内存管理 |
|---|---|---|
| 普通变量 | 自动申请存储空间 | 栈区自动处理 |
| 指针变量 | 自动申请指针空间,无存储空间 | 栈区自动处理,非栈区手动处理 |
- 强制指针转化
利用一个虚拟地址(整数),强制转化出一个指定类型的指针变量。
int p = 100;
int addr = &p;
int* q = (int*)addr;
// 强制地址转化指针
int x = *q;
int x = *(int*)(&p);
内存空间
- 静态区
静态变量为被
static修饰的变量,全局变量则为在程序原文最外侧所设置的变量。
静态区亦称全局区,由编译器管理,编译一次便生效,程序结束失效。
- 栈区
普通变量或称临时变量以及函数参数等,以及指针变量都在这里。
栈区亦称为临时区,由编译器管理,随着程序运行而改变。
- 堆区
一些自定义结构体以及大型数据都在这里操作,配合指针即可灵活使用。
堆区完全由程序员手动控制,包括内存的申请以及释放等;需注意的是此处易发生内存的各类错误,需谨慎使用。
- 代码区
代码区由编译器管理,主要用于存储函数体的二进制原文。
指针空间
指针空间泛指一些指针可以活动的区域,主要包括栈区指针区,部分堆区;按照使用类型又可分为引用其他变量的空间,以及使用自己申请的空间。
| 引用区域 | 内存管理 | 备注 |
|---|---|---|
| 堆区 | 程序设计者管理内存 | 需严谨设计以免发生内存错误 |
| 栈区普通变量 | 自动内存管理 | 普通变量消失时,指针需变为NULL |
| 栈区指针变量 | 自动内存管理 | 指针链接指针即为多级指针 |
- 指针引用自己申请的空间 堆区
int* p;
p = (int*)malloc(sizeof(int));
*p = 100;
free(p);
- 指针引用其他普通变量的空间 栈区普通区
int p = 100;
int* m;
// &p 取一个普通变量的 起始地址
m = &p;
- 指针引用其他指针变量的空间 栈区指针区
int p = 100;
// 一级引用
int* m;
m = &p;
// 二级引用
int** n;
n = &m;
**n = 1000;
-
多级指针
多级指针的内在形式主要包括前面几个节点所指出的,栈区指针,堆内部指针之间的自我引用以及相互引用。且多级指针在操作上和单级指针别无原理上差别。
多级指针需由内向外或由外向内嵌套操作。
- 单级指针:引用其他存储空间,或手动申请堆空间,其后作为普通变量即可。
- 多级指针:逐级引用下一级别指针,最后将其作为普通指针使用即可。
数组以及指针
数组索引,本身就是一个指针,但其没有指针空间地址,仅有存储空间头地址。
(int)p以及(int)&p都是一个数值,都是存储空间头地址。
数组,一段连续的内存空间,由定义方式不同导致栈区和堆区之中都可以存在。
- 数组定义
为确定数组存储空间大小,可在定义阶段初始化数组,否则必须给定数组长度。
int p[] = {0,1};
int p[2];
p[0] = 0;
p[1] = 1;
- 数组的指针意义
数组索引可以完全当作指针来使用。
(int)(&p)与(int)p都可以正确获取头指针位置。
int p[] = {0,1,2,3,4};
int addr = p;
- 指针运算
指针运算仅支持
+-操作,且位移距离为指针所指向的空间的大小。当数组做指针运算时,是向上或向下移动一整个数组的位置。
int p[] = {0,1,2,3,4};
// 数组名直接就是地址值
int* q = p;
// 向量运算支持自增自减
q++;
// 数组的两种访问方式
printf("%d %d %d %d %d",p,*q,*(q+1),*(q+1)+1,p[2]);
- 指针数组与数组指针
指针数组是指一个由指针所组成的数组,本质是一个标准数组。
数组指针是指一个数组的索引完全由一个指针替代,本质是一个标准指针变量。
// 此处是指 p[10] 的 基本组成单位为 int*
int* p[10];
// 此处是指 (*p)[10] 的基本组成单位为 int
int (*p)[10];
动态指针
多级指针可以拿来当数组使用。
函数
函数是一种结构化编程的思维方式。
- 函数组成部分
返回值限定
函数名定义
函数参数定义
实际操作之中,还包括内存管理,异常处理,权限管理等
int example(int x,int y){
return x+y;
}
函数的传参
函数的参数定义在编译阶段就已完成,所以参数的地址是固定的,但是参数的地址仅仅函数内部是可读的,且无法将函数参数地址从函数之中取出。如果可以拿出来会出现严重的系统性安全问题。
- 值传参 标准变量
将原始值的一个
copy传递给函数,参数和原始值是完全分离的。
- 址传参 指针变量
其参数是一个指针,其是将原始参数的存储地址赋予参数的存储地址,保证参数指向原始参数,此时参数的指针空间地址不变,而所保存的指向空间则变为数据的存储地址。
void demo_int(int x){
printf("值传递-> 参数存储空间:%d 参数值:%d\n",(int)&x,x);
}
void demo(int* x){
printf("址传递-> 参数指针空间:%d 参数存储空间:%d 值:%d\n",(int)&x,(int)x,*x);
}
void main(){
int x = 10;
int z = 11;
int* y = &x;
printf("原始存储空间:%d 值:%d\n",(int)&x,x);
printf("指针存储空间:%d 值:%d 指针空间:%d\n",(int)y,*y,(int)&y);
demo(&x);
demo(y);
demo(&z);
demo_int(x);
demo_int(z);
}
- const
const 本身就是一个只读的修饰符。
// 等价于 int const p,p 只读变量
void demo(const int p){}
// 指针不可变,指向的内存空间则无需关心
void demo(const int* p){}
// 指针不可变,指向的内存空间依然不可变
void demo(const int* const p){}
函数与逻辑
- 逻辑判断
所有的逻辑判断必须保证所有路径都被覆盖到,否则会出现严重漏洞!
int true = 1;
int false = 0;
if(true){}
if(true){}else{}
if(true{}elseif(){}else{}
int key = 0;
switch(key){
case 1:
example;
break; //如无此语句,则会直接静茹下一次循环
case 1:
example;
break;
default:
example; //此处一般无需 break
}
- 循环
for语句头部定义的变量为for块级变量。
break,continue。
goto 语句,基本很少用。
// for头部定义的变量为块级别变量,仅在for内部有效,且定义可覆盖外部定义。
for(int i=0;i<=10;i++){
printf("%d",i);
}
while(true){}
do{}while();
STA:
goto STA;
函数指针
函数指针就是将函数引用指针化。
(int)add (int)&add都可正确获取函数的开始地址。
(int)p (int)&p 前者获取指针函数所指向函数的地址,后者获取指针本身的地址。
int add(int x,int y){
return x+y;
}
void main(){
// 指针函数定义
int (*p)(int,int);
p = &add;
printf("%d\n",(*p)(10,13));
printf("原函数地址:%d %d\n",(int)add,(int)&add);
printf("指针函数地址:%d 指针函数所包含的函数地址:%d\n",(int)&p,(int)p);
}
- 指针函数
就是返回值是指针类型的函数。
常用库
C 的标准库文件很少,但是确实强大。
常用方法
- 内存管理
此处所有的内存管理都是基于堆内存的处理。
-
void* malloc(int size)申请 size 大小的空间,并不初始化。 -
void* calloc(int num,int size)申请 num 个 size 大小的连续空间,且每个字节都被初始化为 0。 -
void* realloc(void* addr,int new_size)对 addr 重新分配内存,新空间大小为 new_size 。 -
void free(void* addr)所有申请的空间都需要从这里释放,不及时释放或丢失空间地址会造成内存泄漏。
- 文件管理
c语言的文件管理有种面向对象的感觉,但实际就是一个结构体。
r w a r+ w+ a+ 等即为文件模式的表示符号。
FILE *p = NULL;
p = fopen("./example.txt","w");
fclose(p);
p = NULL;
-
字符串
char 用
'c'表示,string 用"str"表示。
c语言的string就是一个char类型数组,和堆很相似。
- strcpy(s1,s2)
- strcat(s2,s2)
- strcmp(s1,s2)
- strlen(s)
- strchr(s,ch)
- strstr(s1,s2)
// char 转为string
char p[] = {'1','2','3','\0'};
puts(p);
// 有效长度是4
char q[] = "4321";
printf("%s",q);
- 输入输出
-
prinf("hellow %d",p) scanf("%d",p)
-
puts(s) gets(s)
-
char a = getchar() putchar(a)
-
fprintf() fscanf() 文件IO
- 其他库
- stdio.h
- stdlib.h
- string.h
- math.h
- error.h
- ctype.h
- ......
- others
- system("cls")
- system("pause")
const int* a 与 int const *a 作用相同。此时无法对 a 指向的变量做修改。
const int* const a 则指所指向的变量与指针本身皆不可修改。
// 此时修饰的是 const (int* a) 故实际是,值无法改变
const int* a
// 此时修饰的是 const (a) 归实际是指针本身无法改变
int* const a
// 下面这两个东西有数组和string的影子,会出现BUG,尽量少使用。
calloc()
memset(maze,0,size);
运算符
- 常用运算符
- 算术运算:+ - * / % ++ --
- 关系运算:== != > >= < <=
- 逻辑运算:! && ||
- 位运算 :& | ^ ~ >> <<
- 赋值运算:= += -= *= /= %= <<== >>== &= ^= |=
- 特殊运算:sizeof() & * ( ? : )
位运算是一种二进制对位运算
| 类型 | 符号 | 1 | 0 |
|---|---|---|---|
| 与运算 | a & b |
a,b全都为1 | a,b至少有一个0 |
| 或运算 | a ` | ` b | a,b至少一个1 |
| 亦或运算 | a ^ b |
a,b不等 | a,b相等 |
| 反运算 | ~a |
a为0 | a为1 |
位移运算 >> << 则是直接执行位移操作,0用以补位
- 运算符执行等级
c语言之中存在很多的运算符,各个运算符冲突之时,会优先执行等级高的运算。
// p++ 等级比较高 再到 ++p
() [] -> . ++ -- (type) * & sizeof() ...... ,

浙公网安备 33010602011771号