 
                    C语言基础
linux编程预科:
编译器:
gcc,make
c编译过程编译过程:
流程:源文件–1预处理–3编译–4汇编–5链接–可执行文件(.c –> .out)
1.
   预处理:  gcc -E hello.c
- 保存预处理文件 gcc -E hello.c > hello.i 
- 编译: gcc -S hello.i 
- 汇编: gcc -c hello.s 
- 链接并指定生成文件为hello: gcc hello.o -o hell 
 - 对上面过程进行包装: gcc hello.c 即可在当前目录生成可执行文件,也可以用make 进行编译 make hello (make操作不用带后缀) 
测试编译后的文件 : ./hello
vim 编辑器的使用技巧:
运行 vim  /etc/vimrc    对vim编辑器进行修改
建议  cp /etc/vimrc ~/.vimrc   复制一份在自己的家目录,这样 这个vim只对当前用户有效,而不会修改所有用户的vim
想查看 函数原型 直接在vim 中光标放在函数上 按shift+k
gcc编译器的使用技巧:
gcc hello.c -Wall 编译并打印所有警告
echo $? 打印上一行命令的输出状态
shell的使用技巧:
ls -l 可以用 ll 代替
编程的基本要求:
- 头文件要正确包含
- 以函数为单位进行程序的编写
- 声明部分+实现部分
- return 0;
- 空格、注释、对齐的中要求
- 算法:解决问题的方法(用流程图、ns图、有限状态机FSM进行算法逻辑设计)
c语言:(TYPE NAME =VALUE)
c语言注释技巧:
直接在 通过预编译跳过的形式注释:
| 1 | 
 | 
代码块注释:
| 1 | /* | 
基本数据类型:
记住整型int 是占一个机器字长就可以,在32位机器上int 是32位的,其余的 双精度 单精度 的字长都是以int 为基础进行放缩的,例如 在32位机器上double 占64位、float 占32位、short 是16、char占8位。
不能抛开是多少位机器,谈论 不同数据类型所占 位数长度,
有符号和无符号只是 表示的数值范围不同,二者所占的字节数是一样的。
变量的类型及生命周期和作用范围:
auto 默认,会随机给初值 (分配空间),自动收回空间
register (建议型)寄存器类型,只能定义局部变量
static 静态型 禁止函数的外部拓展,初值位0,或空
extern 说明性,不能改变被说明的变量的值或类型
逻辑运算符:
这部分比较简单随用随查
输入输出(I/O):
- 格式化输入输出函数: scanf 、printf - int printf(const char *format, …) - format: “%[修饰符] 格式字符”,动态实参 - 变参 、和定参的区分方法 - 小知识:printf也是有返回值的 - 1 
 2
 3- printf("[%s:%d]before while().\n",__FUNCTION__,__LINE__);//可以打印此句话所在的函数和行号 
 function()
 printf("[%s:%d]after while().\n",__FUNCTION__,__LINE__);//可以打印此句话所在的函数和行号- format: 抑制符 * - %s 的使用是比较危险的,因为不知道存储空间的大小 - scanf 放在循环结构中要注意能否接收到正常的有效内容 
- 字符输入输出函数:getchar、putchar 
- 字符串输入输出函数:gets(!)、puts 
 gets 是十分危险的函数,可以用 fgets 或者getline来代替
流程控制(三大结构 –顺序、选择、循环):
选择: if-else switch-case
小知识: else 只与离他最近的if相嵌套
 case 中只能放常量或常量表达式
循环: while do-while for if-goto
 while 和do while 的区别,要了解到do -while 可以多执行一次
 if-goto(慎用,goto实现的是无条件跳转,且不能跨越函数跳转)
辅助控制: continue break
break 跳出本层循环 ,continue 跳出本次循环
数组
数组的特点 :在内存中连续存放
数组名本身就是表示地址的常量 所以调用数组的时候不用在 数组名前面写取地址符号。
一维数组:
初始化 : 部分初始化 全部初始化 不初始化 static
数组名: 是表示地址的常量,也是数组的起始位置,
数组越界:只能靠程序员的经验来检查,IED不会报错,因为越界是通过指针偏移来找对应的内存地址的,即使那一块不属于数组的范围,但内存中只要存在 就不会报错。
排序方法: 冒泡法 、选择法、快速排序法
删除法求质数
二维数组:
二维数组行号可以省略
字符数组:
数组是不能直接复制的 ,用strcpy()来 赋值,还可以用strncpy()来拷贝给字符数组相应的值,可以防止越界现象。
string.h的头文件里面还有好多函数可以可以设置类似的strncat strcat strcmp strncmp等 ,其中strcmp比较的是相应位置的ascii码
sizeof(str)算末尾的/0 ,strlen(str)不算末尾的/0
printf(“%s—>%d\n”,__ FUNCTION__ ,sizeof(a)) 这句话中 __ FUNCTION__会打印当前行所在函数名。
指针
已知: int *p=a (其中a为一维数组) 可以推出下面的两行结论
a[i]=*(a+i)= * (p+i)=p[i]
&a[i]=a+i =p+i=&p[i] 注意数组名存放的是数组的起始地址(见解:我个人理解数组本身也是一个指针)
指针可以节省传参的资源开销
一个指针变量在内存中占4个字节(32位机器),若是64位机器则占8个字节。
变量与地址:
指针与指针变量:
直接访问与间接访问:
空指针与野指针:
空类型:void ,不太确定指针的类型的时候就用void 类型
指针运算: * & 比较地址
指针与数组: 涉及到指针与数组的一些相关知识,
const与指针:
常量指针:
 定义: 又叫 常指针,通俗理解 就是这是个指针但是这个指针指向常量。这个常量是指指针的值(地址),而不是地址指向的值。 形式 int const* p ; const int* p
 关键点:常量指针指向的对象不能通过这个指针来修改,但仍然可以通过原来的定义来修改。(错误示范 *p =10),函数传入参数如果不希望被误改 可以指定传入参数为常量指针
 常量指针可以被赋值为变量的地址,之所以叫常量指针,是为了限制通过这个指针修改变量。
 指针还可以指向别处,因为指针本身是个变量,可以指向任意地址
指针常量:
 定义 : 本质是个常量,而用指针修饰它。指针常量的值是指针,因为这个值是常量,所以不能被赋值。 形式 int const* p; const int* p;
 关键点:
 指针常量是 一个 常量
 指针所保存的地址可以改变,然而指针所指的值却不可以改变
 指针本身是常量,指向的地址不可以改变即p 不能改变。但是指向的地址所对应的内容可以变化。即 *P可以发生改变。(错误示范 p =&t)
指针数组与数组指针:
| 1 | // 指针数组 : | 
 int * arr[3]
 数组指针:
 int(*p)[3]
多级指针:
函数
| 1 | 
 | 
函数传参:
| 1 | 
 | 
值传递:
地址传递: 比较重要 ,核心理解 点就是 ,指针的内涵
全局变量:
函数的调用(递归和嵌套):
递归是嵌套的一个特例
| 1 | 
 | 
函数与一维数组:
| 1 | 
 | 
小知识:不同位数操作系统下不同的变量类型所占字节数如下,指针是随操作系统位数变的,而int 还有其他类型基本不咋变化。
| c类型 | 32 | 64 | 
|---|---|---|
| char | 1 | 1 | 
| short int | 2 | 2 | 
| int | 4 | 4 | 
| long int | 4 | 8 | 
| long long int | 8 | 8 | 
| char* | 4 | 8 | 
| float | 4 | 4 | 
| double | 8 | 8 | 
| 1 | 
 | 
| 1 | 一维数组,传参时的实参与形参对应表 | 
函数与二维数组:
| 1 | 
 | 
| 1 | /* | 
| 1 | 二维数组,传参时的实参与形参对应表,核心就是判断是不是行指针 或者列指针 | 
二维数组名相当于是一个行指针
二维数组相当于 一个 数组指针 int (*p)[N] ,在写小函数形参的时候要写成这样
指针函数: 返回值 如 int * fun(int)
| 1 | 
 | 
函数指针: 类型 如 int(*p)(int);
| 1 | 
 | 
函数指针数组:
         类型 如  int (*arr[N])(int);
| 1 | 
 | 
指向指针函数的函数指针数组:
 形式 int *(*funcp[N])(int)
构造类型:
结构体
- 产生及意义:把不同类型的数据,存放在连续的空间 
- 描述: struct 结构体名 -  { -  数据类型 成员1; -  数据类型 成员2; -  ………… -  }; 
- 嵌套定义: -  - 1 - 嵌套的结构体可以直接完整的嵌入内部,也可以在外面单独定义这个嵌套的小结构体,但要注意嵌套在内部要在结构体后面写上成员,嵌在里面的小结构体只是相当于一个 数据类型 。 
- 定义变量(变量、数组、指针),初始化及成员引用 -  - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 struct simp_st
 {
 int i;
 float f;
 char ch;
 };
 int main()
 {
 //TYPE NAME = VALUE
 struct simp_st a={123,456.789,'w'};//结构体初始化。 TYPE 是struct simp_st
 //对指定成员赋值 struct simp_st a={.i=147,.ch='s'};
 //指定成员赋值方法2 struct simp_st *p = &a; //引用方法 p->i ,p->ch 等
 //指定成员赋值方法3 struct simp_st arr[2]{{123,123.123,'c'},{456,456.456,'b'}} //使用方法p =&arr[0]; for(;;i++,p++)注意p是结构体所以p++每次跳一个结构体大小,若p是整形p++一次跳4个字节,p是double则p跳 diuble类型所占字节数
 a.i=2358784886;
 printf("%d--%f--%c",a.i,a.f,a.ch);
 exit(0);
 
 }
- 结构体的对齐原则参考资料:从存储原理的角度来看这就是牺牲空间的原则来减少时间的消耗,具体就是跳过了其中一些空间来减少时间的消耗,结构体的总大小为其成员中所含最大类型的整数倍。 在一些网络传输过程中,要注意 对齐方式可能不一样。这时候就需要设置不对齐方法如下。 - 1 
 2
 3
 4
 5
 6- struct a 
 {
 int i;
 char ch;
 float f;
 }__attribute__((packed));
- 函数传参: 方式(值,地址) 。值传参是临时深拷贝,开销非常大。通常会使用指针传参 开销比较小,只有一个指针大小的开销,比如32位操作系统开销是4个字节,64位操作系统是8个字节 
- 无头结构体:只能在声明的时候把变量定义好。 -  
- 微型学生管理系统: -  - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 struct student_st
 {
 int id;
 char name[NAMESIZE];
 int math;
 int chinese;
 };
 void stu_set(struct student_st *p,const struct student_st *q)
 {
 //可以 p->id = q->id;
 *p = *q;
 }
 void stu_show(struct student_st *p)
 {
 printf("%d %s %d %d\n",p->id,p->name,p->math,p->chinese);
 
 }
 void stu_changename(struct student_st *p,const char *newname)
 {
 //错误示范 p->name = newname ,左边是常量。指针是变量但是指针所指的内容是常量,是不能直接这样赋值的。
 strcpy(p->name,newname);
 
 }
 void menu(void)
 {
 printf("\n1 set\n2 change name\n3 show\n");
 printf("Please enter the num of you want to choice(q for quit):\n");
 }
 int main()
 {
 struct student_st stu,tmp;
 char newname[NAMESIZE];
 int choice;
 int ret;
 do{
 menu();
 ret = scanf("%d",&choice);//按理来说每个scanf和printf都有可能出错,要进行校验,要设置出错推出选项
 if(ret !=1)
 break;
 switch(choice)
 {
 case 1:
 printf("Please enter for the stu[id name math math chinese]:\n");
 scanf("%d%s%d%d",&tmp.id,tmp.name,&tmp.math,&tmp.chinese);
 stu_set(&stu,&tmp);
 break;
 case 2:
 printf("Please enter the new name: ");
 scanf("%s",newname);
 stu_changename(&stu,newname);
 break;
 case 3:
 stu_show(&stu);
 break;
 default:
 exit(1);
 }
 sleep(1);//#include <unistd.h>
 
 
 
 }while(1);
 
 exit(0);
 }
共用体:
- 产生及意义:相当于结构体中的成员有多个,但同时只能一种情况存在。多个成员公用一块空间,当在使用其中一个成员时,其他成员是不生效的。这点可以从内存占用反应出来。 
- 类型描述: -  - 1 
 2
 3
 4
 5
 6- union 共用体名 
 {
 数据类型 成员名1;
 数据类型 成员名2;
 .......
 };
- 嵌套定义: 与结构体差不多,且共用体可以嵌套结构体,结构体也能嵌套共用体 
- 定义变量(变量、指针、数组),初始化及成员引用: - 成员引用: 第一种 变量名.成员名 第二种 指针名->成员名
 
- 占用内存大小: 公用体的大小是根据它里面最大的那个成员 来分配的。 
- 函数传参(值、地址): 记住一点 地址传参开销小。 - 实现高四位加上低四位的例子:
 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 //方法一:传统方法依靠位运算
 int main()
 {
 uint32_t i = 0x11223344;
 printf("%x\n",(i>>16)+i&0xFFFF);
 }
 /*
 //方法二:共用体和结构体嵌套
 union
 {
 struct
 {
 uint16_t i;//占两个字节
 uint16_t j;//占两个字节
 }x;//占四个字节
 uint32_t y;//占四个字节
 }a;//占四个字节
 int main()
 {
 a.y = 0x11223344;
 printf("%x\n",a.x.i+a.x.j);
 }
 */
- 位域: 没有实际开发使用的意义 ,但也是一个知识点。存放变量不是以字节为单位而是以位为单位。 -  - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 union
 {
 struct
 {
 char a:1;//占一个位
 char b:2;
 char c:1;
 }x;//总共占4位
 char y;//8位
 }w;//总共占8位
 int main()
 {
 
 }- 大端和小端的概念:大端指数据存储时,数据的高位保存在低地址中,数据的低位保存在高地址中。 常用的x86体系结构,多数都是小端 。c51是非常典型的大端。很多arm 和dsp都是小端结构。
 -  
枚举:
- 相对比较简单,这里用一个例子说明,把枚举当宏来使用,方便查错,预处理之后报错 报的是名,而不是宏的值: 1 2 3。但是它不能当成函数传参来使用,而宏可以。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 enum
 {
 STATE_RUNNING = 1,
 STATE_CANCELED,//默认是2
 STATE_OVER //最后一句不需要逗号,这里默认是3
 
 };
 struct job_st
 {
 int id;
 int state;
 time_t start ,end;
 };
 int main()
 {
 struct job_st job1;
 /*获取任务状态*/
 switch(job1.state)
 {
 case STATE_OVER:
 break;
 case STATE_CANCELED:
 break;
 case STATE_OVER:
 break;
 default:
 abort();
 }
 
 }
动态内存管理:
- 几个相关函数: - malloc void *malloc(size_t size); 申请指定大小的内存空间
- calloc
- realloc(已经用了一部分内存,想再申请一部分,若用掉的部分内存后面的位置被占用了,则找一大块相应的连续内存 将此任务已经用掉的部分内存内容剪切过去,相当于重新分配了一块连续的大内存)
- free void free(void *ptr) 释放内存 ,free之后把指针设置为空指针 ,这样可以避免野指针的出现
 
- 原则:谁申请谁释放 - 重构学生管理系统的例子:(主要对名字的最大占用空间做了改变) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 struct student_st
 {
 int id;
 char *name;
 int math;
 int chinese;
 };
 void stu_set(struct student_st *p,const struct student_st *q)
 {
 p->id = q->id;
 p->name = malloc(strlen(q->name)+1);//加1是预留一个尾0
 if(p->name ==NULL)
 exit(1);
 strcpy(p->name, q->name);
 p->math = q->math;
 p->chinese = q->chinese;
 }
 void stu_show(struct student_st *p)
 {
 printf("%d %s %d %d\n",p->id,p->name,p->math,p->chinese);
 
 }
 void stu_changename(struct student_st *p,const char *newname)
 {
 free(p->name);
 p->name = malloc(strlen(newname)+1);
 strcpy(p->name,newname);
 
 }
 void menu(void)
 {
 printf("\n1 set\n2 change name\n3 show\n");
 printf("Please enter the num of you want to choice(q for quit):\n");
 }
 int main()
 {
 struct student_st stu,tmp;
 char newname[NAMEMAX];
 int choice;
 int ret;
 do{
 menu();
 ret = scanf("%d",&choice);//按理来说每个scanf和printf都有可能出错,要进行校验,要设置出错推出选项
 if(ret !=1)
 break;
 switch(choice)
 {
 case 1:
 tmp.name = malloc(NAMEMAX);
 printf("Please enter for the stu[id name math math chinese]:\n");
 scanf("%d%s%d%d",&tmp.id,tmp.name,&tmp.math,&tmp.chinese);
 stu_set(&stu,&tmp);
 free(tmp.name);//释放掉临时名存放的空间,做到谁申请谁释放
 break;
 case 2:
 printf("Please enter the new name: ");
 scanf("%s",newname);
 stu_changename(&stu,newname);
 break;
 case 3:
 stu_show(&stu);
 break;
 default:
 exit(1);
 }
 sleep(1);//#include <unistd.h>
 
 
 
 }while(1);
 
 exit(0);
 }
补充知识:
- 一个典型的typedef: - 类型: typedef 已有的数据类型 新名字: 
- 作用:1、为已有的数据类型改名, 2、对数据类型做重定义 这样就可以在程序从32位机器 转到64位机器上运行时可以快速对数据类型进行修改 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36- #define IP int * 
 IP p,q;-----> int *p,q;
 typedef int *IP;
 IP p,q;------>int *p,*q;
 typedefy int ARR[6]; //本质是 int [6]->ARR
 ARR a;------>int a[6]
 struct node_st
 {
 int i;
 float f;
 };
 typedef struct node_st NODE;
 NODE a;--------> struct node_st a;
 typedef struct node_st *NODEP;
 NODEP p;---->struct node_st *p;
 //还可以简单点
 typedef struct
 {
 int i;
 float f;
 }NODE,*NODEP; //直接对无名的结构体改名 既可改成NODE ,也可改成 *NODEP
 typedef int FUNC(int);
 FUNC f;---->int f(int);
 // int *(*p)(int); 指向函数指针的指针函数
 
- makefile工程文件 - make定义: make是一个集成的工程管理器, 
- makefile定义: make执行的一个脚本,程序员一般写一个大写的MAKEFILE 在里面对编译过程进行说明, 
- .h文件不参与编译 ,makefile中写的是依赖关系 
 
数据结构
- 本文标题:linux学习经验
- 创建时间:2023-02-28 11:57:44
- 本文链接:2023/02/28/linux修炼手册/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!