应用开发01-- C语言基础(李慧芹)-- 学习笔记

1 基础

1.1 GCC与VIM的使用

main函数模板

/*头文件*/
#include<stdio.h>
#include<stdlib.h>
/*函数入口*/
// 调用多个参数
int main(){
    return 0;
}
// 传入多个参数
/*
int main(int argc, char **argv){}
*/

程序编译过程

预处理:将宏文件、条件编译、头文件复制粘贴到代码中。
gcc -E demo.c >>demo.i
编译:将二进制代码编译成汇编语言,进行语法检查。
gcc -S demo.i
汇编:将汇编语言转换成机器语言,生成目标文件。
gcc -c demo.s
链接:将目标文件、启动代码、库函数连接到一起,生成可执行程序。
gcc demo.o -o demo

GCC编译器

gcc demo.c                              直接生成a.out
gcc demo.c -o demo               生成demo可执行文件
make demo                             通过名字找到文件并调用 gcc demo.c -o demo 完成编译
gcc demo.c -Wall                    显示所有隐藏警告
gcc demo.c demo.h main.c     一次编译多个文件

VIM的使用

配置文件:vim /etc/vimrc(全用户)、cp /etc/vimrc  ~/.vimrc 本用户使用。
设置空格数目:vim ~/.vimrc   添加一行(set ts=4)。
切换模式:一般模式(esc)、命令行模式(:)、插入模式(i)。
设置与取消行号: :set nu 、 :set nonu 。
去文档头部:一般模式下 gg 。
去文档尾部:一般模式下 G 。
去指定的行::100 。
找到函数:光标停留在函数名,一般模式下gd 。
自动补齐变量:一般模式下Ctrl + p 。
格式化文档:v进入可视化模式 按下 = 。
查看命令帮助文档:光标停在函数名,shift + k 跳转到man手册,qq退出。
安装man的包:yum -y install man-pages 。
1.2 编程注意问题
1. 头文件的重要性。
2. 以函数为单位进行编程(便于封装提高代码复用性)。
3. 声明部分(全局变量、头文件、函数)放在最上面,函数的局部变量放在函数头部。
4. 写返回值,exit结束当前线程,父进程为Shell,谁调用谁就是父进程, echo $? 打印上一句结束状态 。
5. 善于注释:
	单句:/*单句注释*/
	多句:/**
		    *   @变量名:解释
		    *   功能与注意
		    */
		#if 0
		注释语句,不参与编译部分
		#elif 1
		参与编译部分
		#endif
6. 算法:结局问题的方法(流程图、NS图、有限状态机FSM)。
7. 防止写越界、内存泄漏,谁打开谁关闭,谁申请谁释放。
1.3 基本数据类型
char     short    int    long     float     double     unsigned 
1. - 所占字节
(1)标准c只规定了int为一个机器字节,double大于int,char小于int 。
(2)只规定了范围并未规定具体大小,不同机器不同,sizeof   %zd 查看数据类型所占字节。
2. 存储区别
(1)有符号与无符号数的存储空间一样,只是二者解释方式不同,最高位为符号位。
(2)c语言的数据可以是十进制、十六进制、八进制,但不能直接打印二进制。
3. 不同数据类型之间转换
(1)隐式转换:运算时,自动将所有数据转换为参与运算的最高精度数据类型。
(2)显示转换:强制转换,加(),存在精度损失。
4.特殊性
(1)布尔型:#include <stdbool.h>  bool flag = true;   true = 1,false = 0 。
(2)浮点数大小比较:fabs (a - b) < 1e-6 。
(3)char 是否有符号不知道。
(4)不同形式的0:字符 '0'、数字 0 、字符串 “0”、空字符 ‘\0'  = 00H 。
(5)数据类型需要与输入匹配。
(6)没有单位的常量没有意义,需要加LL或者F或者L。
1.4 常量
常量:执行时值不发生改变的量
分类:
    (1)整型 1
    (2)实型 1.1
    (3)字符型 单引号包裹的单个字母或者转义字符 'a' '\t' '\ddd'(八进制) '\xdd'(十六进制)
    (4)字符型 双引号包裹的一个或多个字符序列  "a" "a\t\007"
    (5)标识常量 宏
    	#define PI 3.14  #define 宏名 宏体
    	在预处理期间,使用宏体替换宏名,不做代码检查,占用编译时间,不占用内存时间,更快
    	但宏会可能导致错误,对时间要求高时使用(系统内核)
    	函数不易出错,但占用运行时间,对稳定性要求高的时候使用(应用)
    	1. 严格加括号 #define F (a+b) 
    	2. MAX问题,标准c无解
    	#include <stdio.h>
    	#include <stdlib.h>
    	#define MAX(a,b) (a) > (b)? (a):(b)
    	int main(){
    		int a = 1,b = 2,c = 0;
    		c = MAX(a++,b++);
    		printf("%d\t%d\t%d\n",a,b,c); // 2 4 3
    		exit(0);
		}
		a++调用一次 b++调用一次 c 在调用一次后赋值
        函数可以避免该问题 宏也可以修改
        #define MAX(a,b) ({typeof(a) A=a,B=b;(A) > (B)? (A):(B);}) 2 3 2
        相当于比较后再++
1.5 变量
变量:程序在执行过程中值不断变化的量。
定义:[存储类型] 数据类型 标识符 = 值;
标识符:由字母、数字、下划线组成,且数字不能作为开头的标识序列(相当于内存空间的名字)。
数据类型:基本数据类型 + 构造数据类型。
存储类型:
	(1)auto(默认):变量存储在栈空间中,GCC自动分配与释放。
	(2)register(寄存器类型):建议GCC将变量存在寄存器中,但不一定会放在寄存器中。
						      变量只能是局部常量,变量的地址不能获取或者打印,变量的大小只能为对应机器字长,不为double、float、long。
	(3)static(静态类型):修饰变量,自动初始化一次,且具有继承性,修饰函数与变量只能为当前c文件和函数使用,一般向外开放一个调用接口。
	(4)extern(说明型):函数在其余地方声明初始化,在另外的文件中需要使用,就extern 数据类型 变量名,///其数据类型无法改变///。
局部变量与全局变量:
	(1)全局变量,初始化一次,在函数调用时会改变变量值,影响其他函数调用该变量,全局变量不能在多个文件中定义。
	(2)局部变量,函数调用时创建,不会自动初始化,默认为机器值。
	
就近原则:函数名相同时,遵循就近原则。
预编译宏:__FUNCTION __     % s  打印当前函数名             __LINE__     %d  打印当前行号

在这里插入图片描述

1.6 运算符
(1)%参加运算的参数只能为整数。
(2)/ 整数参加运算结果为取除数,小数参加运算结果为小数。
(3)&&,短路与,前面的表达式结果不满足时,不进行后一步的运算。
(4)复合运算: a = 1; a -= a * = a += 3;
			  a -=  a * =  a = a +3
              		 a -=  a * = 4
              		 a -= 16;
              		 a = 0;
(5)位运算:
	① 将第i位置为1:n = n | (1 << i);
	② 将第i位置为0:n = n & ~(1 << i);
	③ 获取第i位数据:if(n & 1 << i) 

在这里插入图片描述

在这里插入图片描述

1.7 流程控制
(1)else 与最近的if匹配。
(2)while 与 do while 的判断条件一样,进入过循环后的结果一样,while最少0次,do while最少循环1次。
(3)loop  ; if () goto loop;  实现无条件,且不能跨函数。
(4)死循环:while(1)    for( ; ; );    ctrl + c   退出循环。
(5)break 跳出循环;continue 进入下一次循环。
(6)不满足报错:switch (){default : fprintf(stderr, " INPUT ERROR!\n");}   if(){}else if(){}else{错误判断} 。

2 标准IO

2.1 格式化输入输出

格式化输出

查询帮助文档:man 3 printf
定义:int printf (const char *format, ...);   (”[修饰符] 格式字符“,输出表项)
返回值:打印字符的数量,不包括'\0',printf不知道自己需要输出多少个变量,没有格式符的会输出相邻栈的机器值。
刷新输出:需要’\n'来刷新缓冲区,当中间有Sleep(5)时,会等睡眠完后两个输出一起执行。
printf("%*.*f", 0, 6, n);  按照%0.6f来输出

在这里插入图片描述

在这里插入图片描述

格式化输入

查询帮助文档:man 3 scanf 。
定义:scanf ("[修饰符] 格式符",变量地址);数组不加& 。
返回值:正确获取的变量个数。
注意:
(1)两个输入格式串中间不能加'\n',加逗号需要输入时逗号拼接,加空格时,以空格、tab、回车来拼接。
(2)字符串输入时很危险,会超过字符串定义长度越界,读取字符串遇到空格、回车会停止输入。
(3)不检查输入格式,循环时遇到错误格式会导致死循环,需要while(scanf("%d",i) == 1)来限制输入,不满足就打印错误。
(4)连续两个scanf,第二个scanf获取字符,会导致第二个获取了回车,需要加getchar() 或者 %*c%c 用限制符消除第一个回车。
调试代码:当代码出现错误,将可能出错的位置处加上输出语句查看变量值。
当调Sqrt时需:gcc demo.c -lm   vim makefile    添加 CFLAGS+=lm 。
2.2 字符输入输出

字符输入

查询帮助手册:man 3 getChar
char ch = getChar();
从标注输入中读入一个unsigned char ,并将其转换为int或者EOF。
getChar不能紧接着scanf,否则会获取回车。

字符输出

查询帮助手册:man 3 putChar
putChar(Char ch)
打印ch 到标准IO屏幕上。
2.3 字符串输入输出

字符串输入

查询帮助手册:man gets
char *str = gets();
从标准输入中读入一行或直到遇到EOF时,将其输入到s指针指向的缓冲区,并在末尾添加一个'\0'。
注意:gets() 不检查当前缓冲区当前栈的溢出,除非遇到写保护区域,否则会超过定义的长度,读取更多字符。
用fgets或getLine取代。

字符串输出

查询帮助手册:man puts
puts(char * str);
将str写到屏幕上,并追加回车。

3 数组

3.1 一维数组
(1)定义:[存储类型]  数据类型  标识符 [下标];  下标为常量或常量表达式。
(2)初始化:int nums [] = {,,};  int nums [s] = {,,}; 部分初始化 int nums[3] = {1};  {1,0,0};
(3)求长度:sizeof(nums)/sizeof(int)    下标从0到len-1 。
(4)数组名:数组名为数组首元素地址。
(5)数组越界:当数组元素越界时,仍然可以获取元素,但该元素为数组尾部地址后面的地址。
(6)静态数组:static int nums[]   数组元素自动初始化。
3.2 冒泡排序
void BubleSort(int *nums, int Len){
    int i,j,temp;
    for(i = 0;i < Len - 1; j++){
        for(j = 0;j < Len - 1 - i; j++){
            if(nums[j] > nums[j+1]){
                temp = nums[j+1];
                nums[j+1] = nums[j];
                nums[j] = temp;
            }
        }
    }
}
3.3 选择排序
void ChooseSort(int * num, int Len){
    int i, j, k;
    for(i = 0;i < Len - 1;i ++){
        k = i;
        for(j = i + 1; j < Len;j ++){
            if(a[j] < a[k]){
                k = j;
            }
        }
        if(i != k){
            j = nums[k];
            nums[k] = nums[i];
            nums[i] = j;
        }
    }
}

/*思路:
	 第一轮,将第一个元素的下标保存,分别与后面的元素值比较,
	如果当前下标对应的元素比后续元素大,则保存后续元素下标
	直到k中存储的下标对应值最小,再将最小值与第一个值交换位置
	持续到i = Len - 1
*/
3.4 二进制转换
#include <stdio.h>
#include <stdlib.h>

int main(void){
    int num; // 存放一个十进制数
    int i = 0;
    int map[128]; // 存放二进制位数
    printf("please enter the decimal\n");
    if(scanf("%d",&num) != 1){
        printf("the input is not right\n");
        exit(0);
    }
    #if 0
    while(1){ 
        map[i++] = num%2;
        num /= 2;
        if(num == 0){
            break;
        }
    }
    #endif
    do{
        map[i] = num %2;
        num /= 2;
    } while(num != 0);
    // output the number
    printf("the conver number is :\n");
    for(i--;i >= 0;i--){
        printf("%d",map[i]);
    }
    printf("\n");
    return 0;
}

3.5 删除法求质数
#include <stdio.h>
/*
 * 第一轮,以2为起始循环值,将2的倍数全部删除再进入下一轮
 * 第二轮删除未删除的3的倍数的值
 * 未删除前数组值为0
 * 删除后数组值为-1
 * */
static void primer(int n){
    int len = n+1;
    int *map = malloc(sizeof(int)*(len));
    int i,j;
    for(i = 0;i < len;i++){
        map[i] = 0;
    }
    for(i = 2;i < len;i++){ // 从2开始循环
        if(map[i] == 0){
            for(j = 2*i;j < len;j+= i){
                map[j] = -1;
            }
        }
    }
    for(i = 0;i < len;i++){
        if(map[i] == 0){
            printf("[%d] = %d\n",i,map[i]);
        }
    }
}
int main(void){
    int n = 100;
    primer(n);
    return 0;
}
3.6 二维数组
(1)定义:[存储类型]  数据类型  标识符 [行] [列];其中行与列需要用到宏定义。
(2)存储:从第一行到第二行,先顺序存储完第一行,再地址接着第二行。
(3)初始化方式:
	① 全赋值:int map[2][3] = {{1,2,3},{4,5,6}}; --> 1,2,3,4,5,6
	② 部分赋值:int map[2][3] = {{1,2},{4}};   --> 1,2,0,4,0,0
	③ 省略行:int map[][3] = {1,2,3,4}; --> 1,2,3,4,0,0
3.7 二维数组做形参
 (1)方式1:
 	① 定义 
 			void fn (int map[m][n]) 或者void fn (int map[][n],int m);
 	② 调用 
 			fn(map);
 	③ 函数体内操作数组
 			map[i][j] 
 			*(map[i] + j)
 			*(*(map + i) + j)
 			*(int*)map + i *n + j   n 为列宽
(2)方式2:
		       void fn((int *)map[N], int M); 调用同上
(3)方式3: 
		       void fn (int ** map,int row, int col);   
		       调用fn((int**)map,row,col);
		       只能(int *)map + n*i + j;操作数
实际上感觉3种方式都可以使用map[i][j]来操作
#include <stdio.h>
#include <stdlib.h>
#define N 3
#if 0
static void show_arr(int map[][N],int m/*, int map[N][N]*/){
    int i,j;
    for(i = 0;i < m;i++){
        for(j = 0;j < N;j++){
            printf("[%d][%d] = %d\t",i,j,*(int*)map + i *N + j/*, *(*(map+i) + j), *(map[i]+j), map[i][j]*/);
        }
        printf("\n");
    }
}
#endif
#if 0
static void show_arr(int(*map)[N],int row){
    int i,j;
    for(i = 0;i < row;i++){
        for(j = 0;j <N;j++){
            printf("[%d][%d] = %d\t",i,j,*(map[i]+j)/*, map[i][j], *(int*)map + i*N + j*/);
        }
        printf("\n");
    }
}
#endif
static void show_arr(int **map,int row,int col){
    int i,j;
    for(i = 0;i < row;i++){
        for(j = 0;j < col;j++){
            printf("[%d][%d] = %d\t",i,j,*(int*)map + i*col + j);
        }
        printf("\n");
    }
}
/*交换行列*/
int main(void){
    int map[N][N] = {{1,2,3},{4,5,6},{7,8,9}};
    int i,j,k;
    for(i = 0;i < sizeof(map)/sizeof(map[0]);i++){
        for(j = i+1;j < sizeof(map[i])/sizeof(map[i][j]);j++){
            k = map[i][j];
            map[i][j] = map[j][i];
            map[j][i] = k;
        }
    }

    show_arr((int**)map,N,N);
    return 0;
}
/*求最大值及对应行号*/
#define N 3
#endif
static void getMax(int map[N][N],int *max, int *line){
    int i,j;
    for((*max) = map[0][0],(*line) = 0,i = 0;i < N;i++){
        for(j = 0;j < N;j++){
            #if 0
            if((*max) < (*(int*)map + i*N + j)){
                *max = (*(int*)map + i*N + j);
                *line = i;
            }
            #endif
            if((*max) < map[i][j]){
                *max = map[i][j];
                *line = i;
            }
        }
    }
}

int main(void){
    int i = 0;
    int j = 0;
    int map[N][N] = {{1,2,3},{1,5,6},{7,8,4}};
    getMax(map,&i,&j);
    printf("max = %d,loc:%d\n",i,j);
    return 0;
}
3.8 二维数组的存储机制
map[2][3] = {{1,2,3},{4,5,6}};
map        -->  map[0]  --> map[0][0]
    		             --> map[0][1]
    		             --> map[0][2]
map+1   -->  map[1]   --> map[1][0]
    	                      --> map[1][1]
    	                      --> map[1][2]
3.9 字符数组
1. 定义
		[存储结构] 数据类型 标识符[长度]。、
2. 初始化方式
(1)单个字符初始化
		char str[10] = {'a', 'b', 'c'}。
		调用str[i]进行打印。
		puts时后面的元素全为'\0'。
(2)字符串初始化
		char str[10] = "hello"。
		尾部自动添加一个'\0'。
3. 格式化输入输出
(1)scanf("%s",str);
	无法获得有空格、回车、tab的后续字符。
	str前不加&,本身就是一个地址。
(2)printf("%s\n",str);
	需要引入<string.h>头文件。
3.10 string.h头文件中的函数
(1)strlen与sizeof
	strlen和sizeof的格式符都为%zd。
	strlen 不统计字符串后面的伪0。
	sizeof会统计字符串后面的伪0。
(2)strcpy与strncpy
	strcpy(str1,str2); 将str2的内容拷贝到str1,但不会考虑长度够不够,长度不够也能拷贝过去。
	strncpy(str1, str2, n); 从str2中最多只拷贝n个字符到str1中,防止函数越界。
(3)strcat与strncat
	strcat(str1,str2); 将str2的内容拼接到str1后面,但是不会考虑越界。  ① 将str1后面的空字符删去,②将str2拼接到str1后面。
	strncat(str1,str2, n);  从str2中最多取n - sizeof(str1)个字符放在str1后面,防止越界。
(4)strcmp与strncmp
	strcmp(str1,str2);   从左向右将str1的ASCII码减去str2的,直到不为0或结束时(当一个结束时,空为0),获得的值为结果。  判断两字符串相等 !(strcmp(str1,str2))。
	strncmp(str1,str2,n); 只比较前n个字符的结果。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define M 1024
/*统计单词数量*/
int main(void){
    char str[M];
    int i;
    int flag = 0, count = 0;
    printf("please enter a string\n");
    gets(str);
    for(i = 0;str[i] != '\0';i++){
        if(str[i] == ' '){
            flag = 0;
        }else if(flag == 0){
            count ++;
            flag = 1;
        }
    }
    printf("the number of str is %d\n",count);
    return 0;
}

4 指针

4.1 指针与变量
int i = 10;
int *p = &i;
(1)变量:即存储空间的内容10的别名,i就表示10。
(2)指针:即存储空间的地址,通过该地址可以找到10。
4.2 指针与指针变量
(1)指针是一个表示地址的常量。
(2)指针变量是一个存放指针的变量。
4.3 指针运算
(1)取地址符&,解引用符*,关系运算只有比地址先后采用。
(2)int i = 10;
	 int * p = &i;
	 int **q = &p;
	 p = &i。
	 q = &p。
	 *p = *(&i) = i。
	 *q = *(&p) = p。
	 **q = **(&p) = *(&i) = i。
(3)p为指针变量的内容,即i的地址;*p即指针变量内容的指向内容,即i。
4.4 空指针与野指针
1)空指针:int *p = NULL,当不知道p的指向时,先声明一个空指针来占位,预防使用野指针。
(2)野指针:只声明了指针变量,并未指定其指向,而使用了该指针会报错4.
4.5 void 指针
void* 可以存任意类型指针,任意指针也可以存void指针,用来不知道指针类型时使用。
malloc(sizeof(Node))  --> 获得一个void*指针。
4.6 指针与一维数组。
int map[3] = {1, 2, 3};
int *p = map;  // int *p = (int *[3]){1, 2, 3};
取地址: &map[i]  =  &p[i] =  (map + i) = (p + i)。
取内容:p[i] = p[i] = *(map + i) = *(p + i)。
map与p的区别:map为指针常量,不可以修改;p为指针变量,可以修改p++。
数组长度:len = sizeof(map) / sizeof(*map);
4.7 指针与二维数组
1)数组名加1,移动到下一行对应位置。
		int map[2][3] = {{1,2,3},{4,5,6}};
			map[0]     -->     map[0][0]          map
                 	     	                 map[0][1]
        					map[0][2]
			map[1]     -->     map[1][0]          map + 1
        					map[1][1]
        					map[1][2]2)行指针与列指针
        	int * p = map ?  --> 行列指针不兼容
                 p为列指针,加1移动一个单元,map为行指针加1则移动一行。
                 int *p = *map = &map[0][0];  可行。
(3)指针引用数组元素:
                 *(*(map + i) + j)
                 *(map[i] + j)
                 *(int * )map + i * n + j
4.8 指针与字符数组
1)读取后面部分
		char str[] = "hello";   // 分配了固定大小。
		char *p = str  + 2;
		puts(p); // 输出str+2后的所有字符。2char str[] = "hello";   修改:strcmp(str,"world");	str为指针常量,不可以修改
(3char *str = "hello";    修改:str = "world";   		str为指针变量,存"hello"的常量地址,可以修改
4.9 常量指针与指针常量
1)常量:
    		#define PI 3.14                 // 宏体替换宏名,不改变其值,预处理阶段处理,不检查语法
    		const int p = 1;    // 编译时执行,会检查语法,int *q = &p; *q = 2; 可以改变值2)常量指针:
    		const int * p = &i;        // 内容不能改变,指向可以改变,*p = 2(错误),p = &j(正确)。
		    用来函数传常量字符串时,不改变字符串的内容。
(3)指针常量:
    		int * const p = &i;        // 内容可以改变,指向不可改变,*p = 2(正确),p = &j(错误)。4)双constconst int * const p = &i;  //指向和内容皆不可变。5)规律:
    		先看*位置,*const之前为指针常量,*const之后为常量指针。
4.10 数组指针与指针数组
数组指针(行指针):[存储类型] 数据类型 (*指针名) [下标]; 下标为列长度。
				  int (*p)[3] = map;
指针数组(列指针):[存储类型] 数据类型 *指针名 [长度]; 	存储一位数组的数组。
    			 	char *name[5] = {"hello","world",...};      存储五个字符串数组。

5 函数

5.1 函数定义
[存储类型] 数据类型 (形参列表){ 函数体; }
void swap(int &a, int &b){
    int temp = *a;
    *a = *b;
    *b = *a;
}
被掉函数需要声明在主调函数上方
5.2 函数传参
void fn(int a, int b) {}         // 值传递,不可以修改参数内容,只拷贝一份内容
void fn(int &a, int &b){}     // 地址传递,可以修改参数内容
5.3 main函数解析
int main(int argc, char ** argv){
    	int i;
    	printf("argc = %d",argc);
    	#if 0
    	for(i = 0;i < argc;i++){
            	pus(argv[i]);
             }
    	#endif
    	for(i = 0;argv[i] != NULL;i++){
            	puts(argv[i]);
             }
    	return 0;
}
./main   123 456 传入两个参数 argc为2,argv将两个参数传进去以NULL字符结束
也会解析通配符,*
5.4 递归
#include <stdio.h>
#include <stdlib.h>
#if 0
1. 递归终止条件。
2. 递归循环条件。
3. 递归循环公示。
4. 缺点:将每一步压入栈,等到执行到终止条件才一个一个出栈,导致栈空间不足。
#endif
/*阶乘*/
int fn(int n){
    if(n < 0)
        return -1; // 报错
    if(n == 0||n==1)
        return 1;
    return fn(n - 1) * n;
}
/*斐波那契*/
int fn1(int n){
    if(n < 0)
        return -1; // 报错
    if(n == 0||n == 1)
        return 1;
    return fn1(n - 1) + fn1(n - 2);
}
int main(void){
    printf("fn = %d\n",fn(5));
    printf("fn1 = %d\n",fn1(5));
    return 0;
}
5.5 函数与一维数组
1void fn(int *a, int len);     a为指针变量,传入地址,需要指明长度。
(2void fn(int a[], int len);     b为指针常量,也需要指明长度,不可以a++。
(3)调用 fn(map, N);
调用*aa[0]&a[0]p[i]p*p
接收int *intint *intint*int
5.6 函数与二维数组
1)看做一维数组
	fn(int * p, int len)   ---> 调用:fn(*a,n); 或者:fn(&a[0][0], n);2)看做二维数组
	fn(int p[][N], int m, int n);
	fn(int (*p)[N], int m, int n);
	fn(int **p, int m, int n);
	调用:fn(a, m, n);3int a[M][N] = {...};
	int *p = *a;
	int (*q)[N] = a;
调用a[i][j]*(a + i) + ja[i] + jp[i]*pq[i][j]*qq
接收intint*int*intintintint*int(*)[N]
5.7 字符数组
#include <stdio.h>
#include <stdlib.h>
/*字符串拷贝函数*/
char *mystrcpy(char *dest, const char *src){
    if(dest != NULL && src != NULL)
        while((*dest++ = *src++) != '\0');
    return dest;
}
char *mystrncpy(char *dest, const char *src, size_t n){
    int i;
    for(i = 0;i < n && (dest[i] = src[i]);i++);
    dest[i] = '\0';
    return dest;
}
int main(void){
    char str[] = "hello";
    puts(str);
    mystrncpy(str, "world",3);
    puts(str);
    return 0;
}
5.8 函数与指针关系
1)指针函数:返回指针的函数, int * fn(int a,int b);2)函数指针:指向函数的指针,int (*p)(int, int) = max;   调用:p(a, b);3)函数指针数组:存多个函数指针的数组,int (*p[N])(int, int); 调用:p[0](a,b);4)指向指针函数的函数指针数组:int * (*p[N])(int, int);

6 构造类型

6.1 结构体定义
1)结构体定义:
    	struct Node{
            	数据类型 成员1// 只描述结构体成员类型,不可以初始化赋值
                    数据类型 成员2};
(2)结构体嵌套定义:
            struct birthday_st{
                int year;
                int month;
                int day;
            };
            struct student{
                int content;
                char name[N];  // 只能strcpy赋值
                struct birthday_st bth;
            };
6.2 结构体初始化与引用
1)直接初始化:struct Node node = { , , ...};2)部分初始化:struct Node node = {.content = 10, .name = "zhangsan"};3)间接初始化:node.content = 12;
6.3 结构体指针
struct Node * p = &node;
p->content = 21;
(*p).content = 21;
6.4 结构体数组
struct Node list[2] = {{...},{...}};
调用:list[0].content 
6.5 结构体内存问题
1)结构体指针和指针所占据的字节一样。
(2)结构体的内存对齐:按顺序存储结构体内容,地址%sizeof(变量) == 0则将其存下,否则地址加1。
(3)结构体传参:
    	fn(struct Node node);  // 拷贝整个结构体,调用node.xx
	    fn(struct Node *p);       // 传递指针变量,只传地址,调用node -> xx

在这里插入图片描述

6.6 共用体
1)定义
union 共用体名{
    int i;
    char ch;
};
使用同结构体。
(2)共用体存储特点
共用体中只有一个变量有效,存储空间为最大的变量,使用较小变量按照其字节数来解析。
51采用大端模式,arm和x86用小端模式。
(3)结构体嵌套共用体
    struct node_st{
        	union node_un{
                 };
    };4)共用体嵌套结构体
    union node_un{
    	   struct node_st{
           	   };
     }
        /*前16位加后16位*/
        #include <stdio.h>
        #include <stdlib.h>
        #include <stdint.h>
        union{
            struct{
                uint16_t i;
                uint16_t j;
            }x;
            uint32_t y;
        }a;
        int main(void){
            uint32_t i = 0x11223344;
            printf("%x\n",(i>>16) + (i & 0xffff));
            a.y = 0x11223344;
            printf("%x\n",a.x.i + a.x.j);
            return 0;
        }5)位域
            union{
                struct{
                    char a:1;
                    char b:2;
                    char c:1;
                }x;
                char y;
            }a;
            以补码形式存储一个字节,其中a在最右边,b右边第23个。。。
            与大小端存储有关
            %d输出有符号型,会将补码转换成原码
            a.y = 1;   --> 最低位为1,转换成补码为-1
6.7 枚举类型
enum Season_en{
    SPRING, SUMMER = 3, AUTUMN, WINTER
};
按顺序递增,也会从初始化处顺序递增。
enum Season_en season = AUTUMN;
一般较少常量用宏定义,方便预处理调试,较多则用枚举。

7 动态内存分配

7.1 基本函数
1void * malloc(size_t size);  // 分配size个字节的存储空间2void * realloc(void *p, size_t size); // 重为p分配size个字节存储空间3void * calloc(size_t n, size_t size); // 分配一个空间,里面有n个单元,每个单元有size个字节存储空间4void free(void *p); // 释放p指针的存储空间,释放是丢弃这块空间的使用权限,虽然还可以调用,但会出问题,一般加上 p = NULL。5)头文件#include<stdlib.h>
7.2 基本原则
(1)谁申请,谁释放,一般free应该与申请函数放在同一函数或者模块,防止内存泄漏。
(2)内存泄漏,即一块空间始终不能被使用,除非程序停止。
7.3 基本使用
1)申请整数
    int * n = malloc(szieof(int));
    *n = 2;2)申请数组
    int *p = malloc(sizeof(int) * MAXSIZE);
7.4 注意事项
#include <stdlib.h>
#include <stdio.h>
void fn(int *q, int len){
    q = malloc(sizeof(int) * len);
}
int main(void){
    int *p = NULL;
    fn(p, 5);
    free(p);
    return 0;
}1)该程序会出现内存泄漏,主函数中的p有一块地址,q申请了一块地址,并没有将其挡在p的内容中,最后会丢失。
(2)解决方法:
    	void fn(int **p, int n){
    		*p  = malloc(sizeof(int) * n);
	    }
	    fn(&p, n);
	或者将 q 返回用p来接收。
(3)free后的指针仍然可以调用,但是本身已经失去了对其调用的权力,会出现错误,需要置空。

8 typedef的使用

8.1 typedef定义
typedef 数据类型 标识符;
#define 标识符 数据类型
define比较好预定义查看
8.2 typedef的使用
1)定义基本数据
    typedef unsigned int uint;2)定义结构体
    typedef struct{} Node, Node*;3)定义函数指针
    typedef int (*fn)(int) ;4)定义指向指针函数的函数指针
    typedef int * (*fn)(int);5)定义数组
    typedef int arr[];

9 makefile工程

(1)makefile介绍
make是工程管理器:用来管理程序的依赖关系。
make会检查编译的文件的时间戳是否有变化,会将与变化文件相关的包再次编译。
makefile的优先级比Makefile的优先级高,一般使用Makefile。
(2)新建makefile
touch main.c tool1.c tool1.h tool2.c tool2.h
vim * -p 打开所有文件夹
:bn 跳转到下一个文件夹
:bp 跳转到上一个文件夹
ctrl + 6 两文件之间切换
新建:vim Makefile
调用:make
(3)实现
/*main.c*/
#include <stdlib.h>
#include <stdio.h>
#include "tool1.h"
#include "tool2.h"
int main(void){
    mytool1();
    mytool2();
    return 0;
}
/*tool1.c*/
#include "tool1.h"
void mytool1(void){
    printf("tool1 printf\n");
}
/*tool1.h*/
#ifndef TOOL1_H__
#define TOOL1_H__
#include <stdio.h>
#include <stdlib.h>
void mytool1(void);
#endif
/*tool2.c*/
#include "tool2.h"
void mytool2(void){
    printf("tool2 printf\n");
}
/*tool2.h*/
#ifndef TOOL2_H__
#define TOOL2_H__
#include <stdio.h>
#include <stdlib.h>
void mytool2(void);
#endif
(1)gcc *.c -Wall 
(2)gcc main.c tool1.c tool2.c -Wall 
#Makefile
mytool: main.o  tool1.o tool2.o
    gcc main.o tool1.o tool2.o -o mytool
main.o: main.c
    gcc main.c -c -Wall -g -o main.o
tool1.o: tool1.c
    gcc tool1.c -c -Wall -g -o tool1.o
tool2.o: tool2.c
    gcc tool2.c -c -Wall -g -o tool2.o
#Makefile 修改
OBJS=main.o tool1.o tool2.o
CC=gcc
CFLAGS+=-c -Wall -g -o
mytool: $(OBJS)
    $(CC) $^ -o mytool
main.o: main.c
    $(CC) $^ $(CFLAGS) main.o
tool1.o: tool1.c
    $(CC) $^ $(CFLAGS) tool1.o
tool2.o: tool2.c
    $(CC) $^ $(CFLAGS) tool2.o                             
#makefile 最终
OBJS=main.o tool1.o tool2.o
CC=gcc
CFLAGS+=-c -Wall -g -o
mytool: $(OBJS)
    $(CC) $^ -o mytool
%.o:%.c
    $(CC) $^ $(CFLAGS) $@
clean:
    $(RM) *.o mytool -r
#   rm *.o mytool -rf
#   RM = rm -f
#main.o: main.c
#   $(CC) $^ $(CFLAGS) main.o
#tool1.o: tool1.c
#   $(CC) $^ $(CFLAGS) tool1.o
#tool2.o: tool2.c
#   $(CC) $^ $(CFLAGS) tool2.o
注意:每一shell命令前面都只能有一个tab,否则会出现遗漏分隔符
ctrl +鼠标追踪函数
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值