Linux C 的编译流程
C 编译器
- gcc GNU
- msvc windows
- clang 苹果
- intel
- …
- cc 默认Linux c语言编译器
程序设计语言分类
- 编译型 C、C++、java
- 把源代码转换成机械指令(X86 电脑、ARM 手机)
- 编译做了类型安全检查,安全
- 性能高
- 灵活差
- 解释型 Python、JS、java
- 源代码直接在解释器上运行
- 灵活性强、建模与语义的表达高效 ‘*’
- 易学习 ‘*’
- 性能差
编译流程
c语言源文件:
.h 头文件 包含函数和变量声明、宏和数据类型定义
.c 源文件 包含函数定义和实现,预处理语句
gcc hello.c -o hello
以上语句执行过程可拆分为一下流程:
-
汇编,生成汇编代码
gcc -S hello.c -o hello.s -
编译,生成目标文件.o/.obj
gcc -c hello.s -o hello.o -
链接 目标文件、库文件(.so/.dll)和操作系统系统执行相关代码 生成可执行文件 hello/hello.exe
gcc hello.o … -o hello
TCC 编译器
tcc 优化编译器,生成可执行文件较小
C 语言学习
设计者:丹尼斯·里奇 & 肯·汤普森 共同设计实现 UNIX,设计C语言
UNIX/C、C/Linux、Linux/互联网共生相互成就
推荐纪录片《操作系统革命》
CGI 用C动态生成HTML页面
Web/PHP 基于c改进的
编程范式(方法学、建模时思维模式)
- 面向过程:过程、操作,C、Pascal
- 面向对象 OOP:C++、java、python
- 函数式:Lisp
学习内容
- 变量与类型系统
- 流程控制:顺序、分支、循环
- 数组
- 函数:程序基本单位;C++中为类、对象
- 指针 ‘*’ 信任开发者,驾驭起来有难度 unix/linux root用户
- 结构体
- 库:文件、网络、进程、线程(提高性能,如找一个亿以内的质数)、GUI、安全、MySQL、Opencv …
推荐书:《C语言程序设计 现代方法》
C变量与类型
程序,为完成特定任务,数据与指令的集合
变量与常量
标识符,定义了内存中的一个区域,替代内存物理地址
变量:空间中数据随时间会发生变化
常量:程序中随时间不发生变化 ‘const’
变量声明:类型 标识符| |空间分配的大小 字母下划线开头空间格式 包含字母、下划线、数字不能使用关键字、保留字不能包含空格以及下划线以外的其他符号变量小写、常量大写有意义
类型系统
- 基本类型
- 数值
- 整数
short 2 2^16 = 65536 int 2 4 2^32 = 46亿 10位 long 4 8 long long 8
- 浮点数
float 4
double 8
- 字符
- char 1
- 逻辑
- bool 1
- 数值
- 复合类型
- 数组
复合数据类型,存储多个值,大小不可变
int a[6]; // 6个int元素char c[128]; // 128个char字符char c[] = {'a', 'b', 'c'}; // 3个字符int a[6] = {1, 2, 3, 4, 5, 6}; // 6个int元素int n; int a[n]; // n个int元素,n改变a大小不变, c++中n必须是const
下标可以越界但是结果不确定
- 结构体
- 联合
- 类
- 数组
静态与动态类型
- 静态类型 C、C++、java
- 动态类型 Python、JS
运算
-
算术:+ - * / %
-
位(二进制):& | ^ << >> ~
-
逻辑(比较):
- || 或 左边为假才执行右边,两个假为假
- && 与 左边为真才执行右边,两个真为真
- ! 非 真变假,假变真
-
关系(真假):== != > < >= <=
-
赋值:从右往左执行
-
位运算
数值转换成二进制进行运算,效率更高-
& 与:两1得1,否则为0
- 掩码运算 ip地址掩码
- 奇数偶数判断(n & 1 == 1 为奇)
…
-
| 或:两0得0,否则为1
- 状态叠加
-
^ 异或:两1得0,两0得0,否则为1
- 在所有成对出现的数字中,找只出现一次的数字
- 交换两个变量的值 a = a ^ b; b = a ^ b; a = a ^ b;
-
~ 非:0变1,1变0
-
'>>'右移:右移n位,相当于除以2的n次方 比除法性能高
-
<< 左移:左移n位,相当于乘以2的n次方
-
流程控制
程序结构
-
顺序
-
分支(判断选择)
- if
if (条件) {}
- if else (三元运算符可替代 '? : ')
if (条件) {}else {}
- if else if else (多路分支,匹配范围、枚举值/精确值)
if (条件) {}else if (条件) {}else {//逻辑闭环}
- switch case (多路分支、有限:只能匹配枚举值/精确值)
switch (变量) {case 值:break;......default://逻辑闭环}
-
循环
- for 循环次数确定
//1 初始化 //2 循环条件 //3 步长 //4 循环题 //执行顺序1->2->4->3->2->4->3... for (1; 2; 3){//4 }
- while 循环次数不确定, 先判断再执行
while (条件){}
- do while 循环次数不确定,先执行再判断
do {}while (条件);
重构
不增加代码功能,对代码结构进行改变
函数
命名的代码块,预定义的,可复用的功能单元
函数是c程序的基本单元,c程序有若干个函数组成
函数也叫方法
函数大小:建议不要超过100行/一个屏幕
函数的作用
- 封装:将功能封装成一个函数,便于复用,开放闭合原则
- 可复用
- 模块化:将一个大的功能分解成若干个小功能,便于理解,一般在不同的文件中定义函数
开放闭合原则:对扩展开放,对修改关闭
定义
//函数名 functionName//返回值类型 returnType void int double ptr...//参数列表 parameter list int a, int b double c, int d...returnType functionName (parameter list){//函数体//返回值return returnValue;}
递归
一个函数调用自身
部分递归实现的算法可以使用循环实现,递归代码更简洁
部分循环无法实现的只能使用递归实现 文件遍历、图、汉诺塔
- 递归函数必须有收敛条件
- 包含自身的调用
模块化
/*
* f.h
* 声明函数
*/
int jc(int);
int fib(int);
long long fib2(int);/*
* f.c
* 定义函数
*/
#include "f.h"
//菲波那切数列
int fib(int n){//
}long long fib2(int n){//
}
//
指针
概念
指针:一种数据类型,存储变量的地址
指针可以运算
定义
int i; //64位 4字节 32位 2字节double d; //8字节// 64位 8字节 32位 4字节int *p = &i; //& 取地址符 p保存首地址,int类型规定管辖范围double *p1 = &d;// %p打印地址 *解引用运算符,可以取得指针指向的数据,在声明时表示变量是一个指针printf("int\t%ld %ld %p %d\n", sizeof(i), sizeof(p1), p1, *p1);printf("double\t%ld %ld %p %lf\n", sizeof(d), sizeof(p3), p3, *p3);int a[] = {100, 200, 300, 400}; //数组是复合类型,数组名就是一个指针,指向数组的首地址,不被复制 (a = b 错)int *p;p = &a; //指针指向数组的首地址p = a; //数组名称转化成数组首地址p = &a[0]; //指向数组首元素的地址,就是数组首地址p[i] = 233; //p[i] == *(p+i)printf("%d\n", *(p + i)); //输出233
字符串
定义
字符串:字符构成的一个有‘序列表’,以‘\0’结尾的字符序列
//字符
char c;
//字符数组,字符串中出现特殊字符需要用 \ 转义
char s[] = {'a', 'b', 'c', '\0'};
char s[4] = "abc"; //由于隐含地包含'\0',容量(sizeof(s))要大于表面上的‘长度’(strlen(s)),这里大于4
//字符串,数组,元素内容可变(s1[0] = 'a'),数组不可变(s1 = "abc" X)
char s1[] = "abc";
//字符指针,不改可变元素内容(s2[0] = 'a' X),指针变量本身可变(s2 = "abc")
char *s2 = "abc";
//拼接,初始化的时候可以
char s3[] = "abc""def""ghi\
get";
//拼接,赋值时也可以
char *s2 = "abc""def"
s2 = "abc""get";
sizeof(s):返回字符数组容量,包含‘\0’
strlen(s):返回字符数组长度,不包含‘\0’
数据形式:
- 数值
- 字符串:文本
- 二进制
- 图形
- 音频
- 视频
- …
操作
头文件:string.h
- strlen(s):返回字符串长度
- strcat(s1, s2)、strncat(s1, s2):拼接字符串,s2拼接到s1
- strcmp(s1, s2):比较字符串, 返回值:0(相等),-1(s1 < s2),1(s1 > s2), 其实就是返回最后一个不相等的字符做减法运算后的结果
- strcpy(s1, s2)、strncpy(s1, s2):复制字符串, s2复制到s1
- puts(s)、fputs(s, fp):输出字符串到标准输出或文件,回车结束
- gets(s)、fgets(s, size, fp):从键盘读取字符串,回车结束,最多取size - 1个字符(包括\0)
应用
- 进制转换
char base[] = "0123456789ABCDEF";char res[100];int n;printf("请输入一个正整数:");scanf("%d", &n);int len = 0;while(n > 0){char tmp[100];tmp[0] = base[n % 16];tmp[1] = '\0';strncat(tmp, res, len);++len;strncpy(res, tmp, len);n /= 16;}printf("%s\n", res);
- 大数值计算
- 加法
- 从低位到高位每位相加再加进位,模10的余数放进结果字符数组,除10的结果保存为进位,字符数组长度就是较大数长度 + 1(最多进一位)
- 为方便操作,对不等长的两个数,将较短的数补充0,使其与较长数长度+1相等,较长数也要在最高位之后补充一个0,从低位到高位加,要将字符数组翻转
- 结果要删除多余的0
- 减法
- 不管两个数谁大谁小,用大数做减数,小数做被减数,最后根据大小处理符号
- 从低位到高位每位减,结果小于0要向高位借位(结果加10,高位a[i + 1]减1)
- 为方便操作,对不等长的两个数,将较短的数补充0,使其与较长数长度相等,同时要将字符数组翻转
- 结果要删除多余的0
- 乘法
- 对于被乘数每一位b[i],乘数a累加b[i]次存入tmp,然后tmp后面补i个零,将tmp累加到结果数组中
- 除法
方法一: 大数减小数,能减多少次除数商就是多少
方法二: 模拟短除法的过程- 先处理特殊情况:1- 被除数比除数小,直接输出0 2- 被除数为0,打印错误信息
- 从高位开始模拟,先取除数a与被除数b长度相等的一部分tmp
- 从高位开始tmp减被除数b,结果放tmp,能减的次数count就是结果数组这一位的值,最后不够减的数保存在tmp
- 将a剩余拼接一位到tmp末尾,直到大于除数,或者拼接的是被除数最后一位之后的‘\0’(退出条件),每拼接一位就保存一位0到结果数组,但是结果数组的第一位不能是0,所以如果时一开始的结果,结果没有内容不需要补0,同时遇到退出条件也不用补0
- 重复2-3步,共执行 除数长度减被除数长度 + 1 次
- 为了删除结果多余的0,如果count是0且结果数组是第一位,则不要加到结果中,如果更新结果数组后已经是最后一次循环直接退出
- 算法过程要多次比较两个数的大小,可以定义一个比较函数比较两个字符串表示的数的大小
示例代码:
//交换两个字符
void swap(char *a, char *b)
{int tmp = *a;*a = *b;*b = tmp;
}
//字符串翻转
void reverse(char *s)
{int len = strlen(s);for (int i = 0; i < len / 2; ++i){swap(&s[i], &s[len - i - 1]);}
}
//字符串表示的数字相加运算
void add(const char *a, const char *b, char *res)
{int llen = strlen(a);int rlen = strlen(b);//记录最后结果是正是负int flag = 0;if (a[0] == '-' && b[0] == '-'){flag = 1;--llen;--rlen;}//如果一正一负则委托减法进行运算else{if (a[0] == '-' && b[0] != '-'){sub(b, a + 1, res);return;}if (a[0] != '-' && b[0] == '-'){sub(a, b + 1, res);return;}}//两个都是整数则正常计算//拷贝到临时变量中,避免影响原字符串char tmpa[100];char tmpb[100];if (flag){strncpy(tmpa, a + 1, llen + 1);strncpy(tmpb, b + 1, rlen + 1);}else{strncpy(tmpa, a, llen + 1);strncpy(tmpb, b, rlen + 1);}//计算出最较大的和较小的数字以及其长度int size, minc;char *max, *min;if (llen > rlen){max = tmpa;size = llen + 1;min = tmpb;minc = rlen;}else{max = tmpb;size = rlen + 1;min = tmpa;minc = llen;}//翻转数组reverse(tmpa);reverse(tmpb);//填充0使原字符串最大数字的长度加1for (int i = minc; i < size; ++i){min[i] = '0';}max[size - 1] = '0';//逐位加int cur = 0;for (int i = 0; i < size; i++){cur = cur + max[i] - '0' + min[i] - '0';res[i] = cur % 10 + '0';cur /= 10;}//去除多余的0while (res[size - 1] == '0'){--size;}if (flag){res[size++] = '-';}//补充'\0'res[size] = '\0';//翻转位正常循序reverse(res);
}
//比较两个字符串表示的数组
int compare(const char *a,const char *b)
{int llen = strlen(a);int rlen = strlen(b);if (llen > rlen){return 1;}else if (llen == rlen){return strcmp(a, b);}else{return -1;}
}//字符串表示的数字相减运算
void sub(const char *a, const char *b, char *res)
{int flag = 0;int llen = strlen(a);int rlen = strlen(b);//使用临时数组,不影响原字符串char tmpa[100], tmpb[100];strncpy(tmpa, a, llen + 1);strncpy(tmpb, b, rlen + 1);//比较两个数大小,获取长度,用较大减较小数,最后处理符号//分别获取较大数和较小数char *max, *min;int minc, maxc;//根据符号处理if (a[0] == '-' && b[0] == '-'){--llen;--rlen;if (compare(a + 1, b + 1) >= 0){flag = 1;max = tmpa + 1;min = tmpb + 1;maxc = llen;minc = rlen;}else{max = tmpb + 1;min = tmpa + 1;maxc = rlen;minc = llen;}}//如果一正一负则委托加法法进行运算else if (a[0] != '-' && b[0] != '-'){if (compare(a, b) >= 0){max = tmpa;min = tmpb;maxc = llen;minc = rlen;}else{flag = 1;max = tmpb;min = tmpa;maxc = rlen;minc = llen;}}else{//-a - b =-a + -bif (a[0] == '-' && b[0] != '-'){tmpb[0] = '-';strncpy(tmpb + 1, b, rlen + 1);add(a, tmpb, res);return;}//a - -b = a + bif (a[0] != '-' && b[0] == '-'){add(a, b + 1, res);return;}}//翻转数组reverse(max);reverse(min);//较小数填充0for (int i = minc; i < maxc; ++i){min[i] = '0';}//逐位减int t = 0;for (int i = 0; i < maxc; ++i){t = max[i] - min[i];if (t < 0){t += 10;--max[i + 1];}res[i] = t + '0';}//去除多余0while(res[maxc - 1] == '0'){--maxc;}//添加\0和符号if (flag){res[maxc++] = '-';}res[maxc] = '\0';//翻转成正常顺序reverse(res);
}
//大数乘法
void mul(char *a, char *b, char *res){int llen = strlen(a);int rlen = strlen(b);//初始化结果int size = llen + rlen + 1;memset(res, '0', size);res[1] = '\0';//不改变原数组使用临时变量char tmpa[llen + 1], tmpb[rlen + 1];strncpy(tmpa, a, llen + 1);strncpy(tmpb, b, rlen + 1);int min = llen > rlen ? rlen : llen;char *minc = llen > rlen ? tmpb : tmpa;int max = llen > rlen ? llen + 1 : rlen + 1;char *maxc = llen > rlen ? tmpa : tmpb;//翻转较短字符串reverse(minc);//对较短字符串每一位n,较长位自加n次, 再添加i个0后加到结果中for (int i = 0; i < min; ++i){char tmp[size];strncpy(tmp, maxc, max);int n = minc[i] - '0';//自加n次for (int j = 0; j < n - 1; ++j){add(tmp , maxc, tmp);}//在末尾添加i个零int pos = strlen(tmp);memset(tmp + pos, '0', i);tmp[pos + i] = '\0';//累加到结果中add(res, tmp, res);}
}
//大数除法
void divi(char *a, char *b, char *res){//特殊情况if (b[0] == '0'){printf("除数不能为0\n");exit(-1);}if (compare(a, b) < 0){res[0] = '0';res[1] = '\0';return;}//从高位模拟,高位减被除数,能减多少次就是商这一位的值//取除数与被除数长度相等的一部分int llen = strlen(a);int rlen = strlen(b);char tmp[rlen + 1];strncpy(tmp, a, rlen);tmp[rlen] = '\0';int t = 0;for (int i = rlen; i <= llen; ++i){//记录能减几次int count = 0;while (compare(tmp, b) >= 0){//减去被除数sub(tmp, b, tmp);++count;}//count是0 且是结果第一位不用加到结果if (count != 0 || t != 0){res[t++] = count + '0';}//到达最后一位,直接退出if (i == llen){break;}//拼接剩余数字while(1){strncat(tmp, a + i, 1);++i;//直到大于除数,或者拼接的是被除数最后一位之后的‘\0'if (compare(tmp, b) >= 0 || i == llen + 1){break;}//不是结果的第一位才要补零if (t == 0){continue;}res[t++] = '0';}}res[t] = '\0';
}
结构体
C结构体与C++类的区别
结构体:封装了属性(字段)
类:封装了属性和相关操作
结构体 | 类 | |
---|---|---|
方法学 | 面向过程 | 面向对象 |
字段 | + | + |
函数/方法 | - | + |
继承 | - | + |
赋值操作 | 拷贝 | 指针 |
c++中结构体和类没有区别,结构体是成员的默认访问级别为公有的类
结构体定义
// 把多个相关的字段封装成一个整体
struct student
{char name[16];char tel[11];int score;double height;double weight;struct student *friend;
}struct student s1[40];
// 不仅封装数据还封装方法
class Student {int age;int score;void talk() {}void run() {}
}
结构体填充原则
- 各字段字节对齐
- 结构体整体大小是最长字段的大小
结构体操作
- 赋值
struct date d1;
struct date d2 ={2024, 7, 3};
struct date d3 = {.month = 7, .day = 4};
d1.year = 2024;
d1.mouth = 7;
d1.day = 5;
- 使用
//访问,通过指针(d->year (*d).year)或对象访问(d.year)
void display(struct date *d){printf("%d年%d月%d日\n", d->year, d->month, d->day);
}
void display2(struct date d){printf("%d年%d月%d日\n", d.year, d.month, d.day);
}
内存管理相关
内存管理概念
int a;int *p; //8字节double *d; //8字节void *v; //8字节,类型未知的一个指针变量,不可以直接使用,通过类型转换或者赋值给具体类型指针使用
void*:类型未知的一个指针变量,可以存储任何类型的地址,不可以直接使用,通过类型转换或者赋值给具体类型指针使
内存管理方式
- 系统管理
函数中声明的普通变量,内存空间在栈空间
由系统自动分配和销毁,在函数声明时分配,结束时销毁 - 手动管理
基于内存管理函数创建的,或全局的变量,存储在堆空间
需要手动分配和销毁,若不释放会引起内存泄漏
相关函数
stdlib.h
- malloc(size_t size) 分配
//静态数组,大小不变 int a[4] = {100, 200, 300, 400}; //动态数组,大小可变 int *b = malloc(sizeof(int) * 4);
- free(void *ptr) 释放
- calloc(size_t num, size_t size) 分配并初始化
- realloc(void *ptr, size_t size) 重新分配 如果有剩余空间,则在原地址中扩展,否则开辟新的空间并拷贝至新空间
- memset(void *ptr, int value, size_t num) 初始化
- memcpy(void *dest, void *src, size_t num) 内存拷贝
- …