C++基础入门【2】

C++基础入门【2】

1.指针

指针变量

定义指针变量语法格式:三种书写形式
数据类型* 指针变量名;int * pa;
数据类型
* 指针变量名; int
* pa;
数据类型* 指针变量名; int * pa;
注意:数据类型问题
注意:int
*pa, *pb;
int *pa,pb;
sizeof(pa) = 32位系统4字节/64位系统8字节

取地址与解引用

  • 取地址,即通过取地址操作符“&”,获取其操作数变量的地址
    如: int* p = &a;//p指向a
  • 解引用,亦称取目标,获取指向的内存值如:*p = 20;
  • 注意星号“”在不同上下文中的不同语义
    c = a * b ; // 将a和b相乘
    int
    * p; //说明p是指向int类型变量的指针
    *p = 20; //表示指针p所指向的目标

野指针与空指针

  • 野指针,即指向不可用内存区域的指针
  • 操作野指针将导致未定义的结果,构成潜在的风险。
  • 产生野指针的原因
    一指针变量没有被初始化
    一指针变量所指向的内存已被释放
  • 空指针,即值为0的指针,可用宏NULL表示
  • 任何情况下,操作空指针的结果都是确定的——崩溃——操作系统保证这一点
  • 空指针比野指针更适合作为指针有效性的判断依据

指针运算

在这里插入图片描述

指针与函数

在这里插入图片描述

指针与数组

  • 数组名本身就是一个指针,代表数组的首元素地址
  • 对数组元素进行下标访问的本质,就是对数组名和下标做指针计算的结果解引用
  • arr[i]等价于*(arr+i),先算arr+i,最后解引用*(arr+i)
  • &pa[0] => pa+0 => (pa+0)
  • 与通常意义上的指针变量不同
    – 数组名是个指针常量,不能通过再次赋值令其指向其它数据
    – int a = 10;
    – arr = &a;//不可以
    – arr++;//不可以,目的是让它指向下一个元素

泛型指针—void*(无类型指针)

  • 仅存储内存地址,不指定目标类型
  • *例如: void p = &a;//通过p知道a的类型吗?
  • 目标类型不确定,*不能直接解引用,p; // gcc迷茫了
  • 使用前必须先做数据类型转换
  • 泛型指针做指针计算,以1字节为单位
    – P= Ox1000;
    – P++;//0x1001

常量指针与指针常量

  • 指针变量

      • 特殊性 : 存储地址
      • 普遍性 : 变量
  • 指针变量
    – 1.存储地址 - 4/8
    – 2.变量 - 存储其他地址
    – 3.变量 - 分配内存 - 有自己首地址
    – 4.数据类型 - 指向的内存数据类型
    – 5.*指针变量 - 获取其指向的内存数据

const型变量

  • 被const关键字修饰的变量具有只读属性
  • 必须在定义的同时初始化
  • const int a = 520;
  • a = 521; //不可以

当const作用于指针

  • 常量指针
    – 常量指针,指针变量保存的地址可以改变,指向的内存值不能修改
    – const int* 或 int const*
    – 常量指针常做为函数的输入函数,在避免值复制传递参数开销的同时,有效防止在函数中意外地修改实参

  • 指针常量
    – 指针常量保存的地址不能修改,内存值可以修改
    – int * const
    – 数组名就是指针常量
    – arr++ //不可以,
    *(arr+i)=5;//可以修改内存

  • 常量指针常量
    – 常量指针常量,指针的目标和指针本身都只读
    – const int * const 或 int const * const

在这里插入图片描述
在这里插入图片描述

案例1:指针强转

#include<stdio.h>

int main(void){
	int a = 0x12345678;
	char *p = (char *)&a;//将int类型的指针强制转换为char类型的指针,
	                     //然后赋值给p, p保存a的首地址

	printf("%p\n", &a);	
	printf("%#x\n", *p);//0x78
	p++;//p = p + 1
	printf("%#x\n", *p);//0x56
	p++;//p = p + 1
	printf("%#x\n", *p);//0x34
	p++;//p = p + 1
	printf("%#x\n", *p);//0x12

	printf("--------------------------------------------\n");
	//通过类型转换访问其中两个字节内存
	short *p1 = (short *)&a;//将int类型的指针强制转换为short类型的指针,
	                        //然后赋值给p1, p1保存a的首地址
	printf("%#x\n", *p1);//0x5678
	p1++;
	printf("%#x\n", *p1);//0x1234

	int *p2 = (int *)&a;
	printf("%#x\n", *p2);

	//获取中间两个字节
	char *pp = (char *)&a;
	printf("%#x\n", *pp);
	pp++;
	printf("%#x\n", *pp);
	printf("%#x\n", *(short *)((char *)&a+1));
	char *b = (char *)&a + 1;
	printf("%#x\n", *b);

	short *b1 = (short *)b;
	printf("%#x\n", *b1);
	return 0;
}
0x7ffc101160a4
0x78
0x56
0x34
0x12
--------------------------------------------
0x5678
0x1234
0x12345678
0x78
0x56
0x3456
0x56
0x3456

案例2:访问任意地址

#include <stdio.h>

int main(void){
	int a = 0x12345678;
	void *p = (void *)&a;//保存变量a的地址
	
	printf("任意1字节\n");
	//任意1字节

	char *p1 = (char *)p;
	printf("%#x\n", *p1);
	p1++;
	printf("%#x\n", *p1);
	p1++;
	printf("%#x\n", *p1);
	p1++;
	printf("%#x\n", *p1);
	
	printf("------------------------------\n");
	//任意1字节 - 直接访问
	printf("%#x\n", *(char *)(p + 0));
	printf("%#x\n", *(char *)(p + 1));
	printf("%#x\n", *(char *)(p + 2));
	printf("%#x\n", *(char *)(p + 3));
	printf("------------------------------\n");

	printf("任意2字节\n");
	//任意2字节
	short *p2 = (short *)p;
	printf("%#x\n", *p2);
	p2++;//p2 = p2 + 1 = p2 + 1 * sizeof(short) = p2 + 2
	printf("%#x\n", *p2);
	//任意2字节 - 直接访问
	printf("------------------------------\n");
	printf("%#x\n", *(short *)(p + 0));
	printf("%#x\n", *(short *)(p + 2));
	printf("------------------------------\n");


	printf("任意4字节\n");
	//任意4字节
	int *p3 = (int *)p;
	printf("%#x\n", *p3);
	//任意4字节 - 直接访问
	printf("------------------------------\n");
	printf("%#x\n", *(int *)(p + 0));
	printf("------------------------------\n");

	return 0;
}
任意1字节
0x78
0x56
0x34
0x12
------------------------------
0x78
0x56
0x34
0x12
------------------------------
任意2字节
0x5678
0x1234
------------------------------
0x5678
0x1234
------------------------------
任意4字节
0x12345678
------------------------------
0x12345678
------------------------------

2.字符串

字符串的存储

  • 字符串就是一组字符组成,用双引号括起来表示,用字符’\0’作为结束符
    – “Hello,World ! \0"等价于 “Hello,World !”
    – “hello”” world" 等价于 " hello world"
  • C语言存储字符串的方式
    – 将字符串中每个字符的ASCII码按先后顺序存储在一段连续的内存中,每个字符占一个字节,最后用空字符,即ASCII码为0的字符结尾

字符串的表示

  • C语言表示字符串的方式
    – 字符指针方式
    – char *pstr = “hello” ; //pstr保存字符串的首地址
    – 不能通过pstr修改指向的字符串,例如:pstr[1] = ‘E’
    – 字符数组方式
    – str[] = {‘a’ , ‘b’ ,‘c’ , ‘\0’}; //“abc”
    – str[] = “abc”;
    • 数组名即字符串首地址
    • 可以用字面值初始化,也可以用{}初始化
    • 用{}初始化需要手动显示注明结束符,否则就是一个数组
    • 可以任意修改

案例1:字符指针和字符串数组

#include <stdio.h>

int main(void){
	
	printf("%s\n", "abcd\0");//打印字符串的占位符%s
	printf("%s\n", "abcd");//打印字符串的占位符%s

	printf("%s\n", "1234\0abcd");//1234
	printf("hello,""world\n");//hello,world\n	
							  //数据库 - 语句可以放在多行里

	printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n");
	printf("ppppppppppppppppppppp"
			"bbbbbbbbbbbbbbbbbbbbbbbbbbb\n");

	//字符指针
	char *pstr = "abc";
	printf("*pstr = %s\n", pstr);//打印pstr指向的字符串
	printf("%c %c %c\n",*(pstr+0), *(pstr+1), *(pstr+2));
	printf("%c %c %c\n", pstr[0], pstr[1], pstr[2]);
	printf("%hhd %hhd %hhd \n", pstr[0], pstr[1], pstr[2]);
	//pstr[1]= 'a';//error
	//字符串数组
	char str1[] = "maab";
	printf("str1 = %s\n", str1);//打印字符串
	printf("str1[0] = %c\n", str1[0]);
	str1[0] = 'a';
	printf("str1[0] = %c\n", str1[0]);
	printf("str1 = %s\n", str1);
	printf("sizeof(str1) = %lu\n", sizeof(str1));

	return 0;
}
abcd
abcd
1234
hello,world
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
pppppppppppppppppppppbbbbbbbbbbbbbbbbbbbbbbbbbbb
*pstr = abc
a b c
a b c
97 98 99 
str1 = maab
str1[0] = m
str1[0] = a
str1 = aaab
sizeof(str1) = 5

字符串的输入和输出

  • 字符串的输入
    • scanf(“%s” , …);
    • char *gets(char *s);
    • 从标准输入读取一行字符串,读走换行符即返回,并将所读到的换行符置换为结尾空字符
  • 字符串的输出
    • printf(“%s”,…)
    • int puts(const char *s);
    • 向标准输出写入一行字符串,并追加一行换行符

字符串函数

  • 标准库提供一套专门针对字符串的函数
    – 字符串头文件
    — #include <stdio.h>
    – 字符串长度
    — unsigned int strlen(const char *s)
    – 字符串拷贝
    — char *strcpy(char *dest,const char *src)
    — char *strncpy(char *dest,const char *src,unsigned int n)
    – 字符串连接
    — char *strcat(char *dest,const char *src)
    — char *strncat(char *dest,const char *src,unsigned int n)

字符串处理函数

strcat - string cat - 将两个字符串连接起来
将后面的字符串拼接到前面的字符串后面
strcat(数组名,数组名/字符指针/字符串字面值)
cat - 文件和终端连接起来

strcpy - string copy- 将两个字符串连接起来
将后面的字符串拼接到前面的字符串后面
strcat(数组名,数组名/字符指针/字符串字面值)

案例2:字符串拼接

#include <stdio.h>
#include <string.h>//字符串函数头文件

int main(void){
	//strlen - 不包含'\0'字符的有效字符数
	char *p1 = "abcdefghijklmnopqrstuvwxyz";
	char str[100] = "hello";
	printf("strlen(p1) = %lu\nstrlen(str) = %lu\n", strlen(p1), strlen(str));

	printf("--------------------strcat-------------------\n");
	//strcat
	char str1[50] = "xyz";
	strcat(str1, "abc");//将字符串xyz拼接到字符串abc后面,放到str1数组中
	printf("str1 = %s\n", str1);
	char *p2 = "mn";
	strcat(str1, p2);
	printf("str1 = %s\n", str1);

	printf("---------------------------------------\n");
	printf("str = %s\n", str);
	strncat(str,"1234",2);//将字符串1234的前2个字符拼接到str数组的字符串后面
	printf("str = %s\n", str);

	printf("-------------------strcpy--------------------\n");
	//strcpy
	char str2[20] = "abcd";
	printf("str2 = %s\n", str2);//abcd
	strcpy(str2, "123456");//将字符串拷贝到str2数组中
	printf("str2 = %s\n", str2);//123456
	char *p3 = "mnxy";
	strcpy(str2, p3);//mnxy \0 6 \0
	printf("str2 = %s\n", str2);//mnxy
	strncpy(str2, "987654123", 2);//将字符串987654123的前2个字符串拷贝到str2
	printf("str2 = %s\n", str2);//12xy \0 6 \0

	return 0;
}
strlen(p1) = 26
strlen(str) = 5
--------------------strcat-------------------
str1 = xyzabc
str1 = xyzabcmn
---------------------------------------
str = hello
str = hello12
-------------------strcpy--------------------
str2 = abcd
str2 = 123456
str2 = mnxy
str2 = 98xy

字符串函数

  • 标准库提供一套专门针对字符串的函数
    – 字符串比较
    — int strcmp(const char *s1,const char *s2);
    — int strncmp(const char *s1,const char *s2,size_t n)
    – 使用字符串函数时需要注意
    — 所有字符串都必须以空字符结尾
    — 字符串长度以char为单位且不包含结尾空字符
    — 拷贝和连接的目标缓冲区必须可写且足够大
    — 不能使用关系运算符比较字符串

strcmp
strcmp(字符串,字符串)
从左边到右边一个字符一个字符的比较,直到不相等或者字符串结束
“hello”
“hfllf”
字符串相等,返回0
第一个字符串大,返回1
第二个字符串大,返回-1

案例3:字符串函数

#include <stdio.h>
#include <string.h>//字符串函数头文件

extern void reverse(char *p);

int main(void){
	//strcmp
	int ret = 0;
	ret = strcmp("abc", "abc");
	printf("abc : abc = %d\n", ret);
	
	ret = strcmp("hello", "hellf");
	printf("hello : hellf = %d\n", ret);
	
	ret = strcmp("hello", "hfllo");
	printf("hello : hfllo = %d\n", ret);
	
	ret = strcmp("r", "a");
	printf("r : a = %d\n", ret);
	
	ret = strcmp("a", "r");
	printf("a : r = %d\n", ret);

	printf("---------------------------------------\n");
	char *p4 = "abc";
	char *p5 = "bcd";
	ret = strncmp(p4, p5, 2);
	printf("abc : abd (2) : %d\n", ret);

	return 0;
}
abc : abc = 0
hello : hellf = 1
hello : hfllo = -1
r : a = 1
a : r = -1
---------------------------------------
abc : abd (2) : -1

案例4:字符串反转

//字符串反转
#include <stdio.h>
#include <string.h>

extern void reverse(char *pstr);

int main(void){
	char str[1024] = {0};
	for(;;){
		//gets(str);
		scanf("%[^\n]", str);
		reverse(str);
		puts(str);
	}

	return 0;
}

 //编辑reverse函数实现字符串反转:"123456789" -> "987654321"
void reverse(char *pstr){
	int len = strlen(pstr);//获取有效字符个数
	for(int i = 0; i < len/2; i++){
		char c = pstr[i];
		pstr[i] = pstr[len-i-1];
		pstr[len-i-1] = c;
	}
	printf("pstr = %s\n", pstr);
}
123456
pstr = 654321

字符串数组

  • 二维数组形式的字符串数组
  • char sa[ ][10] = {“beijing”,“tianjin”,“shanghai”,“chongqing”};
sa0123456789
sa[0]beijng\0\0\0
sa[1]tianjin\0\0\0
sa[2]shanghai\0\0
sa[3]chongqing\0
  • 指针数组形式的字符串数组
  • const char * sa[] = {“beijing”,“tianjin”,“shanghai”,“chongqing”};

main函数参数

//例如:./hello 100 200 -> "./hello" "100" "200"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
    //argc:记录命令行终端输入的命令和参数的个数(操作系统自动赋值)
    //./hello -> argc=1
    //./hello 100 200 -> argc=3
    //argv:记录命令行终端中输入的信息对应的字符串首地址
    //./hello -> argv[0] -> 存储字符串./hello的首地址
    //./hello 100 -> argv[0]存储./hello字符串首地址, argv[1]存储字符串100首地址
    //./hello 100 200 -> argv[0]存储./hello字符串首地址, argv[1]存储字符串100首地址
    //argv[2]存储字符串200首地址
    for(int i = 0; i < argc; i++)
        printf("argc = %d, argv[%d] = %s\n", argc, i, argv[i]);
    
    //计算两个数字求和的结果
    if(argc != 3){
        printf("用法: %s 数字1 数字2\n", argv[0]);
        printf("举例:%s 100 200\n", argv[0]);
        return -1;
    }
    
    //atoi - ascii to int
    int a, b;
    a = atoi(argv[1]);
    b = atoi(argv[2]);
    printf("sum = %d\n", a+b);
    
    return 0;
}
tarena@TNV:day15$ ./main 200 500 600 12 3 5
argc = 7, argv[0] = ./main
argc = 7, argv[1] = 200
argc = 7, argv[2] = 500
argc = 7, argv[3] = 600
argc = 7, argv[4] = 12
argc = 7, argv[5] = 3
argc = 7, argv[6] = 5
用法:./main 数字1  数字2 
举例:./main 100 200 
sum = 700

案例:字符串比较函数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

extern int my_strcmp(const char *s1,const char *s2);

int main(int argc, char *argv[]){
	int ret = my_strcmp("abc", "abcd");
	printf("ret = %d\n", ret);

	return 0;
}

int my_strcmp(const char *s1, const char *s2){
	while(*s1){
		if(*s1 != *s2)	return *s1 - *s2;
		s1++;
		s2++;
	}

	return *s1 - *s2;
}
tarena@TNV:day15$ ./my_strcmp
ret = -100

案例:迷你备忘录

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void){
	printf("===============\n");
	printf("   迷你备忘录\n");
	printf("===============\n");
	char event[31][256];//定义初始化二位数组保存事件信息
	//初始化二位数组
	for(int i = 0; i < 31; i++){
		event[i][0] = '\0';//全部给0
	}

	for(;;){
		printf("备忘信息:");

		int nday = -1;
		scanf("%d", &nday);//输入日期

		if(nday < 0 || nday > 31){
			printf("无效日期\n");
			continue;
		}

		if(nday == 0){
			break;//结束死循环,结束循环输入
		}

		scanf("%s", event[nday-1]);
	}

	printf("============================\n");
	printf("     备 忘 信 息 列 表\n");
	printf("============================\n");
	for(int i = 0; i < 31; i++){//打印信息列表
		if(strlen(event[i])){//如果有效字符个数非0,输入备忘信息
			printf("%d日:%s\n", i+1, event[i]);
		}
	}
	printf("============================\n");
	return 0;
}
tarena@TNV:day16$ gcc event.c -o event
tarena@TNV:day16$ ./event
===============
   迷你备忘录
===============
备忘信息:1 study
备忘信息:2 eat
备忘信息:3 work
备忘信息:0 0
============================
     备 忘 信 息 列 表
============================
1日:study
2日:eat
3日:work
============================

3.预处理

文件包含指令

  • #include
    • 将所包含文件的内容粘贴到该指令处
    • 尖括号包含:#include <xxx.h>
      – 先找-l目录,再找系统目录(/usr/include)适用于系统头文件
    • 双引号包含:#include “xxx.h”
      – 先找-l目录,再找当前目录,最后找系统目录 适用于自己编写的头文件
    • gcc -E -o xxx.i xxx.c

宏定义指令

  • #define分类
    • 无参宏定义(常宏量)
      – #define宏名(宏值)
      – 例如:#define PI (3.14)
    • 有参宏定义(宏函数)
      – #define宏名(形参表)(宏值)
      – 例如:#define SQUARE(X) ((X)*(X))
    • #undef
      – 取消一个已定义的宏,令宏名处于未定义状态
//编译过程:
//gcc -E define.c -o define.i 
//vim define.i -> 跳转到末尾查看
#include <stdio.h>

#define PI (3.14) //定义常量宏,其值为3.14
int main(void){
    double r = 10;
    
    //垃圾代码:后期可维护性很差
    printf("面积为%lg\n", 3.1415926 * r * r);
    printf("周长为%lg\n", 2 * 3.14 * r);

    //有效代码:可维护性强
    printf("面积为%lg\n", PI *r * r);
    printf("周长为%lg\n", 2 * PI * r);
    return 0;
}
//gcc -E define1.c -o define1.i
//vim define1.i -> shift+G 文件末尾
#include <stdio.h>

#define SQUARE(X) (X*X) //求平方
#define SQUARE1(X) ((X)*(X)) //求平方
#define ADD(X,Y) ((X) + (Y))
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))

int main(void){
	printf("%d\n",SQUARE(10));//(10*10)
	printf("%d\n",SQUARE(3+7));//(3+7*3+7)
	printf("%d\n",SQUARE1(10));//
	printf("%d\n",SQUARE1(3+7));//((3+7) * (3+7))
	printf("%d\n",ADD(10,20));

	printf("-------------------------------\n");

	int a = 10, b = 30;
	printf("%d\n",ADD(a,b));//((a)+(b))
	printf("%d\n",MAX(a,b));//((a) > (b) ? (a) : (b))
#undef MAX
    //printf("%d\n", MAX(a,b));//(a)>(b)?(a):(b)
	
	return 0;
}
856                                                                                   
857 # 10 "define1.c"
858 int main(void){
859  printf("%d\n",(10*10));
860  printf("%d\n",(3+7*3+7));
861  printf("%d\n",((10)*(10)));
862  printf("%d\n",((3+7)*(3+7)));
863  printf("%d\n",((10) + (20)));
864 
865  printf("-------------------------------\n");
866 
867  int a = 10, b = 30;
868  printf("%d\n",((a) + (b)));
869  printf("%d\n",((a) > (b) ? (a) : (b)));
870 
871  return 0;
872 }
tarena@TNV:day16$ gcc define1.c -o define1
tarena@TNV:day16$ ./define1
100
31
100
100
30
-------------------------------
40
30

预定义宏

两个下划线
在这里插入图片描述

//宏函数演示
#define MAX(x,y)   ((x)>(y) ? (x) : (y))

int main(void){
//#undef MAX
    //printf("%d\n", MAX(a,b));//(a)>(b)?(a):(b)
    
    int* p = NULL;
    //忘记对p赋值了
    if(p == NULL){
        printf("出现了空指针:");
        printf("%s %s %s %s %d\n", 
                __DATE__, __TIME__, __FILE__, __FUNCTION__, __LINE__);
        return -1;
    }
    return 0;
}

tarena@TNV:day16$ vim define1.c
tarena@TNV:day16$ gcc define1.c -o define1
define1.c: In function ‘main’:
define1.c:23:16: warning: implicit declaration of function ‘MAX’ [-Wimplicit-function-declaration]
  printf("%d\n",MAX(a,b));//((a) > (b) ? (a) : (b))
                ^
/tmp/ccE1jNbi.o:在函数‘main’中:
define1.c:(.text+0xc8):对‘MAX’未定义的引用
collect2: error: ld returned 1 exit status

出现了空指针异常
Jul 18 2023 15:23:52 define1.c main 27 

案例:宏的表示

//gcc -D选项使用:通过-D选项给程序传递一个常量红
//gcc -DSIZE=5 -DPRINT=\"hello\" define3.c -o define3
#include <stdio.h>
//#define SIZE (5)
int main(void){
    int arr[SIZE] = {0};

    //初始化数组元素
    for(int i = 0; i < SIZE; i++)
        arr[i] = i + 100;
    //打印数组元素
    for(int i = 0; i < SIZE; i++)
        printf("%d ", arr[i]);
    printf("\n");

    printf("%s\n", PRINT);
    return 0;
}
tarena@TNV:day16$ gcc -DSIZE=5 -DPRINT=\"hello\" define3.c -o define3
tarena@TNV:day16$ ./define3
arr[0] = 100
arr[1] = 101
arr[2] = 102
arr[3] = 103
arr[4] = 104

hello

条件编译指令 - 满足条件编译,不满足编译器

在这里插入图片描述

#define D (100) 

#ifndef D   -> if not define -> 
            -> 如果没有定义D这个宏, 条件成立, 为真
            -> 反之,如果定义D宏, 条件不成立, 为假 

#ifdef D    -> if define 
            -> 如果定义D宏, 条件成立, 为真 
            -> 如果没有定义D这个宏, 条件不成立, 为假 


#elif -> else if 
#include <stdio.h>

#define A (11)
#define B (1)
#define E (100)

int main(void){

#if A == 1
	printf("1\n");
#endif

#if B == 1
	printf("3\n");
#else
	printf("4\n");
#endif

#ifndef C
	printf("c\n");
#else
	printf("not def c\n");
#endif

#ifdef D
	printf("6\n");
#elif E == 10
	printf("7\n");
#else
	printf("8\n");
#endif

	return 0;
}
tarena@TNV:day16$ vim if.c
tarena@TNV:day16$ gcc if.c -o if
tarena@TNV:day16$ ./if
3
c
8
让一套代码适应于不同平台 
例如Linux内核, 运行 x86 arm PowerPC mips ... 

    cpu_init(void){
        //和平台无关代码
        ...(90%)
        #if ARCH==x86 
            x86 cpu的初始化代码 
        #elif ARCH==arm 
            arm cpu的初始化代码
        #elif ARCH=mips 
            mips cpu的初始化代码 
        ....
        #endif 
    }
    要想生成适应于arm平台的代码 
        gcc -DARCH=arm xxx.c yyy.x ... -o ... 

#ifdef D  == #if defined(D)

4.头文件编程

头文件与头文件卫士

在这里插入图片描述
在这里插入图片描述

多文件编程 

mkdir include 
main.c 
a.h 
b.h  
============================================
//main.c
#include <stdio.h>
//#include "a.h"
int a = 520;//定义一次

//#include "b.h"
//#include "a.h"
int a = 520;//定义2次 - 重定义 

int main(void){
    printf("a = %d\n", a);
    return 0;
}

在这里插入图片描述

tarena@TNV:day17$ gcc main.c -o main
tarena@TNV:day17$ ./main
a = 520

大型程序文件分类

  • 头文件:
    – cal.h
  • 源文件:
    – cal.c
  • 主文件
    – main.c

头文件与头文件卫士

  • 头文件中放什么?
    – 包含公共头文件 #include <stdio.h>
    – 宏定义 #define TRUE 1
    – 类型定义 typedef int BOOL
    – 函数声明 int ConnectServer (const char *URL);
    – 外部变量声明 extern const char *g_dns;
    – 例如:cal.h

  • 源文件放什么
    – 全局变量的定义
    – 函数的定义
    – 例如:main.c
    – 源文件包含自己的头文件
    – main.c
    – #include “cal.h”

  • 主文件(main函数所在的文件,例如: main.c)放什么?
    – main 函数和调用
    在这里插入图片描述

tarena@TNV:exec$ gcc main.c cal.c -o main
tarena@TNV:exec$ ./main
110
80

5.Makefile

  • make是一种文件转换工具,通过某种操作,将依赖文件转换为目标文件
  • make命令可以根据makefile脚本中定义的规则,完成从依赖文件到目标文件的转换
  • 只有当依赖文件比目标文件时,make才会重新生成目标文件
  • 具体到C语言程序,可以认为可执行程序依赖于目标模块,而目标模块又依赖于源程序和头文件
  • 从源程序和头文件到目标模块,以及从目标模块到可执行程序所需要的操作就是各种GCC命令
  • makefile需要描述的就是目标、依赖以及从依赖产生目标所需要执行的各种命令
  • makefile的基本语法要素
    目标:依赖
    <制表符>命令1
    <制表符>命令2
  • 一个makefile可以包含多个目标,通过make命令的参数指定期望实现的目标,缺省实现第一个
  • 命令可以是GCC命令,也可以是普通SHELL命令
  • 目标的依赖和/或命令可以为空
Makefile 
就是一个文本文件 - 特殊 - 存储的是文件的编译的规则 
将来可以让程序按照文件中的编译规则进行编译 
Makefile是给make命令来使用的
执行make后,根据Makefile中所指定的规则进行编译 

目标:依赖 
(tab)命令  

目标文件 - 想要得到的文件 
依赖文件 - 目标文件所依赖的文件 
命令 - 如何由依赖文件得到目标文件 


gcc hello.c -o hello  

vim Makefile  
hello:hello.c
(tab)gcc hello.c -o hello 

案例

西红柿,鸡蛋,调料 - 依赖文件 
菜谱 - Makefile 
厨师 - make 
西红柿炒鸡蛋 - 目标 

gcc -c main.c -o main.o 
gcc -c arr.c -o arr.o 
gcc -c cal.c -o cal.o 
gcc main.o arr.o cal.o -o main  

vim Makefile  

在这里插入图片描述

编译规则

tarena@TNV:exec$ cat Makefile

#编译规则1
main:main.o cal.o arr.o swap.o sum.o
		gcc main.o cal.o arr.o swap.o sum.o -o main

#编译规则2
main.o:main.c
		gcc -c main.c -o main.o

#编译规则3
cal.o:cal.c
		gcc -c cal.c -o cal.o

#编译规则4
arr.o:arr.c
		gcc -c arr.c -o arr.o

#编译规则5
swap.o:swap.c
		gcc -c swap.c -o swap.o

#编译规则6
sum.o:sum.c
		gcc -c sum.c -o sum.o

tarena@TNV:exec$ ls
arr.c  arr.h  arr.o  cal.c  cal.h  cal.o  main  main.c  main.o  Makefile  sum.c  sum.h  sum.o  swap.c  swap.h  swap.o
tarena@TNV:exec$ rm *.o main
tarena@TNV:exec$ ls
arr.c  arr.h  cal.c  cal.h  main.c  Makefile  sum.c  sum.h  swap.c  swap.h
tarena@TNV:exec$ make
gcc -c main.c -o main.o
gcc -c cal.c -o cal.o
gcc -c arr.c -o arr.o
gcc -c swap.c -o swap.o
gcc -c sum.c -o sum.o
gcc main.o cal.o arr.o swap.o sum.o -o main
tarena@TNV:exec$ ./main
110
80
-----------------
9
1 2 3 4 5 6 7 8 9 
----------------
p = 10, q = 20
p = 20, q = 10
-------求和------
ret = 5050

Makefile小技巧:

vim Makefile  

依赖 - 直接依赖 

检查:
1.最终目标是否存在 - 不存在 - 直接编译
2.最终目标存在 
    检查 - 最终目标是不是最新的 
        - 源文件的时间戳 - 是否比生成可执行程序的事件更新 
            比最终目标更新 - 重新编译 
            最终目标更新 - 不再重新编译 

Makefile小技巧:
1.变量 
类似于C语言中的宏 
OBJ=main.o  
使用:
    $(OBJ) 等价于 main.o 
2..c文件生成对应的.o文件 
%.o:%.c 
    gcc -c $< -o $@ 

%.o, $@ - 目标文件 - .o文件 
%.c, $< - 依赖文件 - .c文件 
3.伪目标 
目标 - 没有依赖 -> 伪目标 
clean:
    命令 
make clean - clean对应的命令 

伪目标

tarena@TNV:exec$ cat Makefile 
#类似于C语言里面的宏
OBJ=main.o cal.o arr.o swap.o sum.o
BIN=main
CC=gcc
RM=rm

#编译规则1
$(BIN):$(OBJ)
		$(CC) $(OBJ) -o $(BIN)

#编译规则2:.c文件生成对应的.o文件
%.o:%.c
		$(CC) -c $< -o $@

#伪目标
clean:
	$(RM) $(OBJ) $(BIN)
 

6.结构体

  • 结构是一种由若干成员组成的构造类型
  • 结构的成员既可以是基本类型的数据,也可以又是结构类型的数据
  • 结构是─种构造而成的数据类型,在使用结构之前必须先行定义,因此结构也是一种自定义数据类型
  • 结构类型的数据在存储上类似于数组,所有成员按其被声明的顺序,由地址到地址连续(或接近连续)存放
  • 与数组不同,结构的成员可以是不同类型的数据,因此结构也是一种复合数据类型
结构体是分配内存的第三种方法 
    变量 
    数组 

int a;//4字节
int arr[100];//分配大量内存 数据类型保持一致 
有些场合需要分配不同种类的数据类型 
    分配一段内存去描述一个人的信息 
    信息    数据类型 
    姓名    char name[32]
    年龄    unsigned char  
    id     unsigned int  
    工资    double 

int a[5] = {1,2,3,4,5};
    a[0] a[1] a[2] a[3] a[4] - 称之为数组a的5个成员 
        5个成员 - 类型相同 

结构体 - 成员 - 数据类型可以不同 

结构体是用户自己定义的数据类型 - 定义变量 

结构体变量玩法1

在这里插入图片描述

//定义结构体变量玩法1:直接定义结构体变量
#include <stdio.h>
#include <string.h>
int main(void){
    //直接定义结构体数据类型的变量s1并且初始化
    struct{
        char name[32];//存储名字
        int age;//存储年龄
    }s1 = {"james", 33};
    //打印学生信息
    //通过变量名访问其中的成员语法:变量名.成员名
    printf("%s, %d\n", s1.name, s1.age);

    s1.age = 39;
    strcpy(s1.name, "James");

    printf("%s, %d\n", s1.name, s1.age);
    return 0;
}
tarena@TNV:exec$ vim struct.c
tarena@TNV:exec$ gcc struct.c -o struct
tarena@TNV:exec$ ./struct
james, 33
James, 39

结构体变量玩法2

在这里插入图片描述

//结构体变量玩法2
//先声明数据类型,然后再定义变量
#include <stdio.h>
#include <string.h>
//声明结构体数据类型
struct student{
    char name[32];
    int age;
};
int main(void){
    //定义结构体类型变量
    struct student s1 = {"james", 520};
    //定义结构体指针变量
    struct student* ps1 = &s1;
    //通过指针变量访问结构体成员语法:指针变量->成员名
    printf("%s, %d\n", ps1->name, ps1->age);
    
    ps1->age = 134;
    strcpy(ps1->name, "wukong");

    printf("%s, %d\n", ps1->name, ps1->age);
    printf("%s, %d\n", s1.name, s1.age);
    return 0;
}
tarena@TNV:exec$ vim struct2.c
tarena@TNV:exec$ gcc struct2.c -o struct2
tarena@TNV:exec$ ./struct2
james, 520
wukong520, 134
wukong520, 134

结构体变量玩法3–声明描述学生信息的结构体类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 指向结构变量和结构数组的指针
    – 结构指针的计算以整个结构的大小为单位
  • 访问结构的成员
    – 通过结构变量访问其成员,用成员访问运算符“."
    – 通过结构指针访问其目标的成员,用间接成员访问运算符“->"
  • 如果函数的参数是结构类型,那么形参只是实参的拷贝,在函数内部无法修改实参的成员
  • 如果函数的参数是结构指针,并接受实参的地址,那么就可以在函数内部修改实参的成员
//结构体数组
#include <stdio.h>
//声明描述学生信息的结构体类型
typedef struct student{
	char name[32];
	int age;
}stu_t;

int main(void){
	//定义结构体数组
	stu_t stu_info[3] = {
		{"zhangsan", 19},
		{"liszi", 18},
		{"wangwu", 23}
	};

	int size = sizeof(stu_info) / sizeof(stu_info[0]);

	for(int i = 0; i < size; i++){
		printf("%s, %d \n",stu_info[i].name, stu_info[i].age);
	}
	for(int i = 0; i < size; i++){
		stu_info[i].age += 20;
	}
	for(int i = 0; i < size; i++){
		printf("%s, %d \n",stu_info[i].name, stu_info[i].age);
	}


	return 0;
}

tarena@TNV:day18$ vim struct.c
tarena@TNV:day18$ gcc struct.c -o struct
tarena@TNV:day18$ ./struct
zhangsan, 19 
liszi, 18 
wangwu, 23 
zhangsan, 39 
liszi, 38 
wangwu, 43 

结构体和函数的关系

结构体 + 数组 = 结构体数组 

本质上就是一个数组, 只是该数组每个元素都是结构体类型 

stu_t s[50];

int a[3] = {1,2,3};

结构体和函数的关系:
    结构体作为函数的参数 
        建议使用指针 - 效率更高 
        A函数 -> 结构体变量 -> B函数 
        A函数 -> 结构体变量指针 -> B函数 
                   4 / 8 
    结构体作为函数的返回值 
        结构体变量 - 结构体指针 

局部变量 - 函数结束 -   
    内存释放 - 不能返回局部变量的地址 

结构体

#include <stdio.h>

typedef struct student{
	char name[32];
	int age;
}stu_t;

void show(stu_t st){//st = s1
	printf("%s, %d\n",st.name, st.age);
}

//常量指针不可以进行修改,只能读取
void show_p(const stu_t *pst){
	//pst -> age++;
	printf("%s, %d\n",pst -> name, pst -> age);
}

void grow(stu_t st){//st = s1
	st.age++;//修改的是形参st的值,和变量s1没有关系
}

void grow_p(stu_t *pst){//pst = &s1
	pst -> age++;//直接修改了实参s1的age的值
}

stu_t get_stu_info(void){
	stu_t s = {"liubei", 25};
	return s;
}

//局部变量 - 函数结束 - 内存释放 - 不能返回局部变量的地址
//error
/*
stu_t *get_stu_info(void){
	stu_t s = {"zhugeliang", 34};//结构体类型的变量,局部变量
	return &s;//不可以返回局部变量的地址
}
*/

int main(void){
	stu_t s1 = {"xiaobai", 19};
	show(s1);
	printf("---------------------------\n");
	grow(s1);
	show(s1);
	printf("---------------------------\n");
	grow_p(&s1);
	show(s1);
	printf("---------------------------\n");
	show_p(&s1);//s1的age是否会加1呢
	show(s1);
	printf("---------------------------\n");
	
	stu_t stu = get_stu_info();//使用stu保存该函数的返回值
	printf("%s, %d\n",stu.name, stu.age);
	
	printf("---------------------------\n");

	return 0;
}
tarena@TNV:day18$ vim struct2.c
tarena@TNV:day18$ gcc struct2.c -o struct2
tarena@TNV:day18$ ./struct2
xiaobai, 19
---------------------------
xiaobai, 19
---------------------------
xiaobai, 20
---------------------------
xiaobai, 20
xiaobai, 20
---------------------------
liubei, 25
---------------------------

嵌套结构体

在这里插入图片描述

结构体包含不同类型的成员 
    1.结构体类型的成员 
    2.结构体指针类型的成员 
        birth_t* p; 就是指针变量 -> 初始化 -> NULL 
                直到让p指向谁吗? - 不知道 - NULL 

结构体变量 - 初始化 
结构体数组 - 初始化 
结构体成员 - 初始化 
         - 完全一样 

结构体变量 / 指针变量  - 
    只能决定如何访问当前结构体的成员 

结构体内存对齐和补齐 - 

默认 4字节对齐补齐 
1字节对齐补齐 
2字节对齐补齐 
4字节对齐补齐 
8字节对齐补齐 

#pragma pack(n)
....
....
#pragma pack(pop)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结构体成员为结构体类型变量

//结构体成员为结构体类型变量
//描述学生出生日期的结构体
#include <stdio.h>

typedef struct birthday{
	int year;
	int month;
	int day;
}birth_t;

//描述学生信息的结构体
typedef struct student{
	char name[32];
	int age;
	birth_t birth;//结构体类型的成员
}stu_t;

int main(void){
	//定义结构体类型变量
	stu_t s1 = {"James", 29, {2000, 1, 1}};
	stu_t *ps1 = &s1;
	//ps1->name ps1->age ps1->birth
	//成员的访问
	//s1
	//s1.name s1.age s1.birth
	//birth_t birth
	//birth.year birth.month birth.day
	printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
			s1.name, s1.age, s1.birth.year, s1.birth.month, s1.birth.day);
	printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
			ps1->name, ps1->age, ps1->birth.year,ps1->birth.month,ps1->birth.day);	

	s1.age = 43;
	s1.birth.year = 2013;
	printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
			s1.name, s1.age, s1.birth.year, s1.birth.month, s1.birth.day);
	printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
			ps1->name, ps1->age, ps1->birth.year,ps1->birth.month,ps1->birth.day);	

	return 0;
}
tarena@TNV:day18$ vim struct4.c
tarena@TNV:day18$ gcc struct4.c -o struct4
tarena@TNV:day18$ ./struct4
姓名:James,年龄:29岁,出生日期:2000年1月1日
姓名:James,年龄:29岁,出生日期:2000年1月1日
姓名:James,年龄:43岁,出生日期:2013年1月1日
姓名:James,年龄:43岁,出生日期:2013年1月1日

结构体成员为结构体指针类型变量

//结构体成员为结构体指针类型变量
//描述学生出生日期的结构体
#include <stdio.h>
#include <string.h>

typedef struct birthday{
	int year;
	int month;
	int day;
}birth_t;

//描述学生信息的结构体
typedef struct student{
	char name[32];
	int age;
	birth_t *pbirth;//结构体指针类型的成员
	//就是指针变量
}stu_t;

int main(void){
	//定义结构体类型的变量
	stu_t s1 = {"james", 29, NULL};
	//定义birth_t类型变量
	birth_t birth = {1993, 9, 28};
	//给james同学关联对应的出生日期,此时pbirth就会指向一个有效内存
	s1.pbirth = &birth;
	//birth_t *pbirth;
	//pbirth->year ,pbirth->month, pbirth->day
	printf("%s, %d, (%d, %d, %d) \n",s1.name, s1.age, s1.pbirth->year, s1.pbirth->month, s1.pbirth->day);
	strcpy(s1.name, "James");
	s1.age = 52;
	s1.pbirth->year = 1952;
	printf("%s, %d, (%d, %d, %d) \n",s1.name, s1.age, s1.pbirth->year, s1.pbirth->month, s1.pbirth->day);

	return 0;
}

tarena@TNV:day18$ vi  struct5.c
tarena@TNV:day18$ gcc struct5.c -o struct5
tarena@TNV:day18$ ./struct5
james, 29, (1993, 9, 28) 
James, 52, (1952, 9, 28) 

两个圆

#include <stdio.h>
#include <math.h>

#define PI (3.14) //定义常量宏,其值为3.14

//声明描述一个点的位置结构体
typedef struct{
	int row;
	int col;
}pt;

//声明描述一个圆的结构体
typedef struct{
	pt c;//圆心
	int radius;//半径
}cir_t;

cir_t *bigger(const cir_t *p1, const cir_t *p2){
	return (cir_t *)(p1->radius >= p2->radius ? p1 : p2);
}

//计算圆的面积
double area(const cir_t *p){
	return PI * p->radius * p->radius;
}

//计算圆的周长
double cir(const cir_t *p){
	return 2 * PI * p->radius;
}

//计算两个圆心的距离平方
double dis(const cir_t *p1, const cir_t *p2){
	return ( (p1->c.row - p2->c.row)*(p1->c.row - p2->c.row)+(p1->c.col - p2->c.col)*(p1->c.col - p2->c.col) );
}

//计算长方形的面积
double area_c(const cir_t *p1, const cir_t *p2){
	return (p1->c.row - p2->c.row) * (p1->c.col - p2->c.col);
}

int main(void){
	cir_t c1 = {0}, c2 = {0}, *p = NULL;
	printf("请输入一个圆:");
	scanf("%d%d%d", &(c1.c.row), &(c1.c.col), &(c1.radius));
	printf("请输入一个圆:");
	scanf("%d%d%d", &(c2.c.row), &(c2.c.col), &(c2.radius));
	p = bigger(&c1, &c2);
	printf("更大的圆是(%d, %d), %d\n",p->c.row, p->c.col, p->radius);
	printf("圆的面积为:%lg\n", area(&c1));
	printf("圆的面积为:%lg\n", area(&c2));
	printf("圆的周长为:%lg\n", cir(&c1));
	printf("圆的周长为:%lg\n", cir(&c2));
	printf("两个圆心之间的距离平方:%lg\n", dis(&c1, &c2));
	printf("长方形的面积:%lg\n", area_c(&c1, &c2));

	return 0;
}
tarena@TNV:day18$ vim struct6.c
tarena@TNV:day18$ gcc struct6.c -o struct6
tarena@TNV:day18$ ./struct6
请输入一个圆:3 4 5
请输入一个圆:6 8 10
更大的圆是(6, 8), 10
圆的面积为:78.5
圆的面积为:314
圆的周长为:31.4
圆的周长为:62.8
两个圆心之间的距离平方:25
长方形的面积:12

7.联合 - UNION

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

.联合体
联合体 - 判断主机字节序 - 笔试题 
主机字节序 - 电脑存储方式 
大端存储:将数据的低位保存在内存的高地址处,而数据的高位,保存在内存的低地址中。
小端存储:将数据的低位保存在内存的低地址处,而数据的高位,保存在内存的高地址中。

int a = 0x12345678;

判断当前主机字节序 - 
    大端 
    小端 

x86 arm - 大端,小端 - 小端  
PowerPC, 网络 - 大端 

在这里插入图片描述

大端存储:将数据的低位保存在内存的高地址处,而数据的高位,保存在内存的低地址中。
小端存储:将数据的低位保存在内存的低地址处,而数据的高位,保存在内存的高地址中。


int num = 0x11223344;
低地址		-> 		高地址
大端存储:11 22 33 44
小端存储:44 33 22 11
#include <stdio.h>

int main(void){
	union{
		unsigned int u_n;
		unsigned char u_c[4];
	}mb;//直接定义联合体变量mb,分配4个字节

	mb.u_n = 0x12345678;//向4个字节内存中写入了数据0x12345678
	printf("%#x\n", mb.u_n);//0x12345678
	//0x78   0x56   0x34   0x12   u_n
	//__     __     __     __
	//u_c[0] u_c[1] u_c[2] u_c[3] u_c
	for(int i = 0; i < 4; i++){
		printf("%#x ", mb.u_c[i]);
	}
	printf("\n");
	
	
	//0x78   0x56   0x43   0x12   u_n
	//__     __     __     __
	//u_c[0] u_c[1] u_c[2] u_c[3] u_c
	mb.u_c[2] = 0x43;//0x34 -> 0x43
	printf("%#x\n", mb.u_n);//0x12435678

	//使用联合求主机字节序
	mb.u_n = 1;//0x00 00 00 01
	//			__	__	__	__
	//little	01	00	00	00
	//big		00	00	00	01
	if(mb.u_c[0] == 1){
		printf("little\n");
	}else{
		printf("big\n");
	}

	//指针 - 强转获取第一个字节的数据 - 1,little;0;big

	return 0;
}

tarena@TNV:day19$ vim union.c
tarena@TNV:day19$ gcc union.c -o union
tarena@TNV:day19$ ./union
0x12345678
0x78 0x56 0x34 0x12 
0x12435678
little

经典使用场景

在这里插入图片描述
在这里插入图片描述

struct student:
    姓名 性别 班级 

struct teacher:
    姓名 性别 职称  

结构体 套 联合 

联合 - 对于同一块内存的不同使用方式 
#include <stdio.h>

//声明结构体数据类型
typedef struct person{
	char name[32];
	char sex;//'m' 'f'
	char job;//'s' 't'

	union{
		int class;//班级
		char position[10];//位置/职称 讲师 教授
	}category;
}per_t;

int main(void){
	per_t p[2] = {
		{.name = "小明", .sex = 'm', .job = 's', .category.class = 2208},
		{.name = "老张", .sex = 'f', .job = 't', .category.position = "教授"}
	};

	for(int i = 0; i < 2; i++){
		if(p[i].job == 's'){
			printf("姓名:%s, 性别:%c, 班级:%d \n",
					p[i].name, p[i].sex, p[i].category.class);
		}else{
			printf("姓名:%s, 性别:%c, 职称:%s \n",
					p[i].name, p[i].sex, p[i].category.position);		
		}
	}
	return 0;
}
tarena@TNV:day19$ vim union2.c
tarena@TNV:day19$ gcc union2.c -o union2
tarena@TNV:day19$ ./union2
姓名:小明, 性别:m, 班级:2208 
姓名:老张, 性别:f, 职称:教授

8.枚举 - enum

- 没有数据类型 - 文本替换 
枚举 - 有数据类型 - int类型

提高代码可读性 

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>

enum COLOR{
	GREEN,
	BLUE,
	RED,
	YELLOW
};

int main(void){
	printf("%d %d %d %d \n", GREEN, BLUE, RED, YELLOW);
	return 0;
}
tarena@TNV:day19$ vim enum.c
tarena@TNV:day19$ gcc enum.c -o enum
tarena@TNV:day19$ ./enum
0 1 2 3 
#include <stdio.h>

enum RETURN{
	OK,
	FAILED
};//0 1

typedef enum RETURN return_t;

return_t check_enum(int a){
	if(a != 0){
		printf("success\n");
		return OK;
	}else{
		printf("failed\n");
		return FAILED;
	}
}

int check(int a){
	if(a != 0){
		printf("success\n");
		return 0;
	}else{
		printf("failed\n");
		return 1;
	}
}

int main(void){
	printf("%d\n", check(0));
	printf("%d\n", check(100));
	printf("--------------------\n");
	printf("%d\n", check_enum(0));
	printf("%d\n", check_enum(100));
	return 0;
}
tarena@TNV:day19$ vim enum2.c
tarena@TNV:day19$ gcc enum2.c -o enum2
tarena@TNV:day19$ ./enum2
failed
1
success
0
--------------------
failed
1
success
0

9.高级指针(非常核心)

在这里插入图片描述
在这里插入图片描述

指针 - 变量, 数组, 结构体 

只要想要指向的内容在内存中 - 合法 - 都可以使用指针指向该内容 

如何定义指针变量 - 让该指针变量指向该函数 - 使用该函数 

函数名 - 函数首地址 - %p - 如何验证 
    让pfunc指向了add - 成立 
        pfunc == add 

如果使用函数 - 如何使用函数指针变量 - 
    函数指针变量 - 变量 
        普遍性 - 变量 - 指向其他地址 
        特殊性 - 函数指针变量 


定义另一个函数指针变量 - 指向sub函数 
int (*pfunc2)(int, int) = sub;//ok - 麻烦 

typedef - 起别名 

普遍性 - 特殊性 

int* - 指向了int - int 
    int* p;
char* - 指向了char - char 
    char* p;
int (*pfunc)(int, int) - int ()(int, int)

函数的参数 - 函数指针变量作为形参 
    变量 数组 结构体 
    函数 - 回调函数 - Qt - 信号和槽 - 回调函数 
#include <stdio.h>

int add(int x, int y){
	return x + y;
}

int sub(int x, int y){
	return x - y;
}

int main(void){
	printf("main:%p\n", main);
	printf("add:%p\n", add);
	printf("sub:%p\n", sub);

	int ret = 0;
	//如何定义一个指针变量存储函数的首地址
	//函数指针变量 - 指向哪个函数 - add - 函数指针变量 + 参数 + 返回值
	//初始化 - 函数名
	//定义函数指针变量pfunc保存了add函数的首地址
	int (* pfunc)(int, int) = add;
	//ret = add(100, 200);
	//如何使用函数,就如何使用指针变量
	ret = pfunc(100, 200);
	printf("add函数的返回值为:%d\n", ret);//300
	printf("pfunc:%p\n", pfunc);
	
	//让pfunc指向sub函数 - ok
	//pfunc存储了sub函数的首地址
	pfunc = sub;
	printf("pfunc:%p\n", pfunc);
	ret = pfunc(300, 10);// == sub(300,10); == 290
	printf("sub函数的返回值为:%d\n", ret);//290
	printf("pfunc:%p\n", pfunc);

	return 0;
}
tarena@TNV:day19$ vim pfunc.c
tarena@TNV:day19$ gcc pfunc.c -o pfunc
tarena@TNV:day19$ ./pfunc
main:0x40054c
add:0x400526
sub:0x40053a
add函数的返回值为:300
pfunc:0x400526
pfunc:0x40053a
sub函数的返回值为:290
pfunc:0x40053a
#include <stdio.h>

int add(int x, int y){
    return x + y;
}
int sub(int x, int y){
    return x - y;
}

//什么数据类型
//int (*pfunc)(int, int) - 什么数据类型 - 将变量名去掉 - 变量的数据类型
//int (*)(int, int) - 函数指针变量的数据类型 - 不存在 - 自定义数据类型 - 取别名
//typedef int (*pfunc_t)(int, int); - 后续可以直接以pfunc_t定义函数指针变量
//注意:将新的数据类型名字写在*的后面
typedef int (* pfunc_t)(int, int);
int main(void){
    printf("main:%p\n", main);
    printf("add :%p\n", add);
    printf("sub :%p\n", sub);
    int ret = 0;
    //定义函数指针变量pfunc指向add函数
    pfunc_t pfunc = add;
    ret = pfunc(100, 200);
    printf("add函数返回值:%d\n", ret);//300
    //定义函数指针变量pfunc2指向sub函数
    pfunc_t pfunc2 = sub;
    ret = pfunc2(100, 200);
    printf("sub函数返回值:%d\n", ret);//-100
    return 0;
}
tarena@TNV:day19$ vim pfunc2.c
tarena@TNV:day19$ gcc pfunc2.c -o pfunc2
tarena@TNV:day19$ ./pfunc2
main:0x40054c
add :0x400526
sub :0x40053a
add函数返回值:300
sub函数返回值:-100

使用typedef给定义函数指针变量数据类型

#include <stdio.h>

//使用typedef给定义函数指针变量数据类型
typedef int(* pfunc_t)(int, int);

int add(int x, int y){
	printf("enter %s \n", __func__);
	return x + y;
}

int sub(int x, int y){
	printf("enter %s \n", __func__);
	return x - y;
}

int mul(int x, int y){
	printf("enter %s \n", __func__);
	return x * y;
}

int div(int x, int y){
	printf("enter %s \n", __func__);
	return x / y;
}

int mod(int x, int y){
	printf("enter %s \n", __func__);
	return x % y;
}

//定义函数指针数组,数组中的每个元素都是一个函数的地址
pfunc_t a[] = {add, sub, mul, div, mod};

int main(void){
	int len = sizeof(a) / sizeof(a[0]);
	pfunc_t pfunc = NULL;
	int ret = 0;

	for(int i = 0; i < len; i++){
		pfunc = a[i];
		ret = pfunc(200, 20);
		printf("ret = %d\n", ret);
	}

	return 0;
}
tarena@TNV:day19$ vim pfunc3.c
tarena@TNV:day19$ gcc pfunc3.c -o pfunc3
tarena@TNV:day19$ ./pfunc3
enter add 
ret = 220
enter sub 
ret = 180
enter mul 
ret = 4000
enter div 
ret = 10
enter mod 
ret = 0

回调函数 - 信号和槽 - QT - 引以为傲

//回调函数 - 信号和槽 - QT - 引以为傲
#include <stdio.h>

//声明函数指针类型并且取别名
typedef int (* pfunc_t)(int, int);

//定义回调函数add和sub,意思就是可以将这些函数做为函数的参数传递给其他函数
int add(int x, int y){
	return x + y;
}

int sub(int x, int y){
	return x - y;
}

//定义cal函数,想要使用cal函数来调用add函数和sub函数
//将来需要通过函数指针变量来间接调用回调函数
//如果在某个调用函数中,将形参设置为另一个函数的首地址,间接调用对应的函数
//第三个形参pfunc为函数指针类型,函数为首地址
//cal(100, 200, add) -> pfunc = add
int cal(int a, int b, pfunc_t pfunc){
	if(pfunc == NULL){
		return a * b;//如果没有给cal函数传递回调函数,设置一个默认功能,计算乘法功能
	}
	return pfunc(a, b);
}

int main(void){
	//第三个参数传递的是add,为add函数的首地址,此时的cal函数可以间接的调用add函数
	printf("%d\n", cal(100, 200, add));
	printf("%d\n", cal(100, 200, sub));
	printf("%d\n", cal(100, 200, NULL));//使用cal函数默认的功能,计算乘法的结果
	return 0;
}

tarena@TNV:day20$ vim pfunc.c
tarena@TNV:day20$ gcc pfunc.c -o pfunc
tarena@TNV:day20$ ./pfunc
300
-100
20000

指针数组和数组指针

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//数组指针演示
#include <stdio.h>

int main(void){
	int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
	for(int i = 0; i < 3; i++){
		for(int j = 0; j < 4; j++){
			//printf("a[%d][%d] = %d\n", i, j, a[i][j]);
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}

	printf("\n");
	//定义数组指针指向二维数组
	int (* p)[4] = a;
	for(int i = 0; i < 3; i++){
		for(int j = 0; j < 4; j++){
			//printf("a[%d][%d] = %d\n", i, j, p[i][j]);
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}

	printf("\n");
	//二维数组转换为一维数组
	//a -> int * ->数组中每一个元素都是int类型
	int *p2 = (int *)a;//强制将数组指针类型a转换为普通的int 类型指针
	for(int i = 0; i < 12; i++){
		printf("%d ", p2[i]);
	}
	printf("\n");

	printf("\n");
	//一维数组转换为二维数组
	//一维数组 - 二维数组
	//设置 - 数组arr中的每一个元素都是4个int类型的数组
	int arr[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
	int (* p3)[4] = (int (*)[4])arr;//arr - 数组指针 - 数组中元素位4个int类型的数组
	for(int i = 0; i < 3; i++){
		for(int j = 0; j < 4; j++){
			printf("%d ", p3[i][j]);
		}
		printf("\n");
	}	

	return 0;
}
tarena@TNV:day20$ vim pointer.c
tarena@TNV:day20$ gcc pointer.c -o pointer
tarena@TNV:day20$ ./pointer
1 2 3 4 
5 6 7 8 
9 10 11 12 

1 2 3 4 
5 6 7 8 
9 10 11 12 

1 2 3 4 5 6 7 8 9 10 11 12 

1 2 3 4 
5 6 7 8 
9 10 11 12 

二级指针与多级指针

  • 指针变量的地址即指针的指针
  • 将一级指针变量的地址保存在另一个指针变量中即构成二级指针
  • 二级指针是指向一级指针的指针
  • 常用二级指针类型的函数形参,接受一级指针地址形式的实参,以修改调用者指针的目标,或为其分配资源
  • 对一维数组的数组名取地址,得到的不是二级指针而是数组指针

在这里插入图片描述

#include <stdio.h>

int main(void){
	int a = 100;
	int *p = &a;//p为一级指针
	int **pn = &p;//pn为二级指针 - 指针的指针,pn存储的是变量p的首地址
	printf("a = %d\t, &a = %p\t\n", a, &a);
	printf("p = %p\t, &p = %p\t, *p = %d\t\n", p, &p, *p);
	//重点
	printf("pn = %p\t, &pn = %p\t, *pn = %p\t, **pn = %d\t\n", pn, &pn, *pn, **pn);

	printf("**pn = %d\n", **pn);
	**pn = 200;//修改a的值
	printf("**pn = %d\n", **pn);

	return 0;
}
tarena@TNV:day20$ vim ppointer.c
tarena@TNV:day20$ gcc ppointer.c -o ppointer
tarena@TNV:day20$ ./ppointer 
a = 100	, &a = 0x7ffde0aac814	
p = 0x7ffde0aac814	, &p = 0x7ffde0aac818	, *p = 100	
pn = 0x7ffde0aac818	, &pn = 0x7ffde0aac820	, *pn = 0x7ffde0aac814	, **pn = 100	
**pn = 100
**pn = 200

一级指针内容交换–swap函数

实现字符串的交换
char* pa = “hello”;
char* pb = “world”;
定义swap函数实现pa和pb指向的交换.
pa指向"hello"
pb指向"world

#include <stdio.h>

//如果一个函数修改一个指针的内容,必须传递二级指针才可以
//传递二级指针
extern void swap(char **ps1, char **ps2){ // ps1 = &pa, ps2 = &pb
	//ps1 = &pa; *ps1 = *(&pa) = pa;
	//ps2 = &pb; *ps2 = *(&pb) = pb;
	//*ps1 == pa  - char *
	char *temp = *ps1;//temp = pa
	*ps1 = *ps2;//pa = pb
	*ps2 = temp;//pb = temp = pa
}

int main(void){

	char *pa = "hello";
	char *pb = "world";
	printf("pa = %s,&pa = %p\n",pa, &pa);
	printf("pb = %s,&pb = %p\n",pb, &pb);
	
	swap(&pa, &pb);
	printf("\n");
	/*
	char *tmp = pa;
	pa = pb;
	pb = tmp;
	*/

	printf("pa = %s,&pa = %p\n",pa, &pa);
	printf("pb = %s,&pb = %p\n",pb, &pb);

	return 0;
}
tarena@TNV:day20$ vim swap.c
tarena@TNV:day20$ gcc swap.c -o swap
tarena@TNV:day20$ ./swap
pa = hello,&pa = 0x7ffda6c6ce68
pb = world,&pb = 0x7ffda6c6ce70

pa = world,&pa = 0x7ffda6c6ce68
pb = hello,&pb = 0x7ffda6c6ce70

二级指针–字符指针数组

如果在一个函数中修改一个指针的内容, 必须传递二级指针才可以 

二级指针 - 字符指针数组 
char *arr[] = {"hello", "world"};
        //arr[0] - 字符串hello的首地址 
        //arr[1] - 字符串world的首地址
数组arr中的每个元素 - 什么类型 - char*

等价于 

char *p1 = "hello";
char *p2 = "world";
char *arr[] = {p1, p2};//arr[0] = p1, arr[1] = p2;

数组中每个元素都是char*类型的指针(地址)
arr - 数组名 - 数组的首地址 - 存储的元素 - 地址
arr是一个地址,地址上存储的还是一个地址 - arr具有二级指针的意思 
可以定义一个二级字符指针变量保存字符指针数组的首地址 

char **parr = arr;//arr[0]指针变量 arr是arr[0]的首地址 

数组名 - 地址 

    使用parr和arr访问数组元素的时候 - 通用性 
    差异:
        arr++;//error 
        sizeof(parr) = 4/8 


char  a; - 字符
char *a; - 字符

int main(int argc, char* argv[]){
    ...
}

int main(int argc, char** argv[]){

}

char arr[] = {3,4,5};
char *p = arr;//
#include <stdio.h>

int main(void){
	char *arr[] = {"hello", "world"};
	printf("%s %s\n",arr[0], arr[1]);

	char **p = arr;//定义二级指针p指向arr数组
	printf("%s %s\n",p[0], p[1]);

	char *p1 = "hi";
	char *p2 = "jim";
	char *arr1[] = {p1, p2};//arr1[0] = p1, arr[1] = p2
	printf("%s %s\n",arr1[0], arr1[1]);

	return 0;
}
tarena@TNV:day20$ vim ppstring.c
tarena@TNV:day20$ gcc ppstring.c -o ppstring
tarena@TNV:day20$ ./ppstring 
hello world
hello world
hi jim

10.动态分配内存

分配内存的第四种方法 
    变量 
    数组 
    结构体 

int a;
int a[5];
结构体变量;
    都属于静态分配内存的方式 - 占用的内存空间大小编辑代码的时候就已经定死了

如果做到动态的内存空间分配 
#include <stdlib.h>

动态分配内存与释放 – malloc - memory allocation - 内存分配

  • 分配内存
    – #include <stdlib.h>
    – void *malloc(size_t size);
    – 从堆中分配size字节内存
    – 成功返回该内存的起始地址,失败返回NULL
    – 对所分配的内存不做初始化
    – 若size取0,则返回NULL或者一个唯一的地址,保证后续对free函数的调用能够成功
malloc - memory allocation - 内存分配
void* malloc (size_t size);
    分配size个字节的内存空间 - 堆区 - 特点 
        直到使用free函数将其内存空间释放
        直到程序结束 
            - 记得释放内存空间 - free 
    返回值:void* 返回分配内存的首地址 
                 如果分配失败 - 返回NULL 
#include <stdio.h>
#include <stdlib.h>

int main(void){
	int *p = NULL;
	//在堆区中分配8个字节
	p = (int *)malloc(8);
	if(NULL == p){
		printf("memory allocation failed \n");
		return -1;
	}
	printf("分配内存成功,首地址为:%p\n", p);
	//对内存进行处理
	//...

    *(p + 0) = 100;
    *(p + 1) = 200;
    printf("%d, %d\n", p[0], p[1]);
    printf("%d, %d\n", *(p + 0), *(p + 1));

	//不再使用分配的内存空间的时候,将这块内存空间释放
	free(p);//释放在堆区中所分配的内存
	//释放内存后,需要将指针p设置为空指针,避免野指针的出现
	p = NULL;

	return 0;
}
tarena@TNV:day21$ vim malloc.c
tarena@TNV:day21$ gcc malloc.c -o malloc
tarena@TNV:day21$ ./malloc
分配内存成功,首地址为:0x1e8c010
100, 200
100, 200

动态分配内存与释放 – calloc - clear allocation

  • 分配数组并初始化
    – #include <stdlib.h>
    – void *calloc(size_t nmemb,size_t size);
    – 从堆中分配包含nmemb个元素的数组,其中每个元素占size字节
    – 成功返回该数组的起始地址,失败返回NULL
    – 对所分配数组的每个元素,用相应类型的0初始化
//calloc函数 - 也是分配内存
// 1.数组
// 2.初始化为0
#include <stdio.h>
#include <stdlib.h>

int main(void){
	int *p = NULL;
	p = (int *)calloc(4, sizeof(int));//连续分配4个元素的内存并且元素都为4个字节(int)
									//返回内存的首地址赋值给p
	if(p == NULL){
		printf("clear allocation failed\n");
		return -1;
	}

	printf("clear allocation success : %p\n", p);

	//将p看作数组名
	for(int i = 0; i < 4; i++){
		p[i] = i + 100;
	}

	//打印数组的内存值
	for(int i = 0; i < 4; i++){
		printf("%d ",*(p + i));
	}
	printf("\n");

	//调用free函数释放内存
	free(p);
	p = NULL;
	return 0;
}
tarena@TNV:day21$ vim calloc.c
tarena@TNV:day21$ gcc calloc.c -o calloc
tarena@TNV:day21$ ./calloc
clear allocation success : 0x1db0010
100 101 102 103 

动态分配内存与释放 – realloc - reset allocation - 调整分配内存的大小

  • 调整动态内存大小
    – #include <stdlib.h>
    – void *realloc(void *ptr,size_t size);
    – 将ptr所指向的动态内存大小调整为size字节,原内容保持不变,对新增部分不做初始化
    – 成功返回调整后内存块的起始地址,失败返回NULL
    – ptr必须是之前malloc/calloc/realloc函数的返回值
free
    free函数的参数 - 就是malloc函数的返回值 

calloc - clear allocation  
realloc - reset allocation - 调整分配内存的大小 
    比较单独来使用该函数 - 将其和malloc / calloc 配套使用 

void* realloc (void* ptr, size_t size);
    ptr - 调整是ptr所指向的内存空间 
    size - 将ptr指向的内存空间调整为size个字节 
int *p = (int *)malloc(8);
		realloc(p,16);
将p指向的内存空间的字节数调整为16个字节 - 在原有的字节基础上向后面添加
#include <stdio.h>
#include <stdlib.h>

int main(void){
	int *p = NULL;
	//在堆区中分配8个字节
	p = (int *)malloc(8);
	if(NULL == p){
		printf("memory allocation failed \n");
		return -1;
	}
	printf("分配内存成功,首地址为:%p\n", p);
	//对内存进行处理
	//...

	*(p + 0) = 100;
	*(p + 1) = 200;
	printf("%d, %d\n",p[0], p[1]);
	printf("%d, %d\n",*(p + 0), *(p + 1));

	//调用realloc调整内存大小
	printf("调用realloc调整内存大小\n");
	p = realloc(p, 16);//在原先malloc分配的8字节内存后面继续额外多分配8字节内存
						//返回整个内存的首地址给p
	for(int i = 0; i < 4; i++) p[i] = i + 50;
	for(int i = 0; i < 4; i++) printf("%d ", p[i]);printf("\n");

	//不再使用分配的内存空间的时候,将这块内存空间释放
	free(p);//释放在堆区中所分配的内存
	//释放内存后,需要将指针p设置为空指针,避免野指针的出现
	p = NULL;

	return 0;
}
tarena@TNV:day21$ vim realloc.c
tarena@TNV:day21$ gcc realloc.c -o realloc
tarena@TNV:day21$ ./realloc
分配内存成功,首地址为:0x18ab010
100, 200
100, 200
调用realloc调整内存大小
50 51 52 53 

动态分配内存与释放

  • 调整动态内存大小
  • 若ptr取NULL,则等价于malloc函数
    – realloc(NULL,1024);
  • 若size取0,则等价于free函数
    – 和free函数效果相同

员工信息

#include <stdio.h>
#include <stdlib.h>

#define NUM (4)

//想要存储NUM个员工的信息,分配NUM个emp_t类型的存储区

//声明描述员工信息的结构体类型
typedef struct employee{
	char name[32];
	int age;
}emp_t;

emp_t *get_employee_info(void){
	emp_t *p =(emp_t *)malloc(sizeof(emp_t)*NUM);
	emp_t *ptmp = p;//临时备份分配的内存首地址,将来用于返回
	//完成员工信息的初始化
	for(int i = 0; i < NUM; i++){
		printf("请输入员工姓名:");
		scanf("%s", p->name);//将输入的姓名直接放到name成员中
		printf("请输入员工年龄:");
		scanf("%d", &p->age);//将输入的年龄直接放到age成员中
		p++;
	}
	//返回分配的内存的首地址
	return ptmp;
}

int main(void){
	//定义函数分配初始化员工信息的内存
	emp_t *p = get_employee_info();
	printf("\n打印员工的信息\n");
	//打印员工信息
	for(int i = 0; i < NUM; i++){
		printf("%s, %d\n", p[i].name, p[i].age);
	}
	//释放内存
	free(p);

	return 0;
}
tarena@TNV:day21$ vim employee.c
tarena@TNV:day21$ gcc employee.c -o employee
tarena@TNV:day21$ ./employee 
请输入员工姓名:lb
请输入员工年龄:13
请输入员工姓名:ww
请输入员工年龄:14
请输入员工姓名:sj
请输入员工年龄:52
请输入员工姓名:lb
请输入员工年龄:18

打印员工的信息
lb, 13
ww, 14
sj, 52
lb, 18

获取两点坐标,求出中心点位置

从键盘获取两个点的坐标,获取其中心点的位置
两个点
每个点 x y

#include <stdio.h>
#include <stdlib.h>

//声明描述一个点的坐标结构体
typedef struct point{
	int r;
	int c;
}pt;

//声明描述两个点的坐标的结构体
typedef struct{
	pt p1;
	pt p2;
}rect;

pt* midpt(const rect* p){
	pt* pmid = (pt *)malloc(sizeof(pt));
	if(pmid){
		pmid->r = (p->p1.r + p->p2.r) / 2;//中心点的横坐标		
		pmid->c = (p->p1.c + p->p2.c) / 2;//中心点的纵坐标		
	}
	return pmid;
}

int main(void){
	rect r = {0};//定义了结构体变量,具有了两个点
	printf("请输入两个点的坐标:");
	scanf("%d%d%d%d", &(r.p1.r), &(r.p1.c), &(r.p2.r), &(r.p2.c));

	pt* p_mid = midpt(&r);
	if(p_mid){
		printf("中心点的位置是(%d,%d)\n", p_mid->r, p_mid->c);	
	}
	free(p_mid);
	p_mid = NULL;

	return 0;
}

tarena@TNV:day21$ vim mid.c
tarena@TNV:day21$ gcc mid.c -o mid
tarena@TNV:day21$ ./mid
请输入两个点的坐标:3 4 6 8
中心点的位置是(4,6)

获取两点坐标,求出长方形面积

练习 -
从键盘获取两个点的坐标, 以这两个点为对角的两个点的长方形,
计算这个长方形的面积.

#include <stdio.h>
#include <stdlib.h>

typedef struct{
	int r;
	int c;
}pt;

typedef struct{
	pt p1;
	pt p2;
}rect;

double area_p(const rect p){
	return ((p.p1.r - p.p2.r) * (p.p1.c - p.p2.c));
}

int main(void){
	rect r = {0};//定义了结构体变量,具有了两个点
	printf("请输入两个点的坐标:");
	scanf("%d%d%d%d", &(r.p1.r), &(r.p1.c), &(r.p2.r), &(r.p2.c));
	double area = area_p(r);
	printf("长方形的面积为:%lg\n", area);

	return 0;
}
tarena@TNV:day21$ vim rectangle.c
tarena@TNV:day21$ gcc rectangle.c -o rectangle
tarena@TNV:day21$ ./rectangle
请输入两个点的坐标:0 0 5 5
长方形的面积为:25

11.文件操作

文件操作 


打开文件后可以做的操作 
    读取 
    写入 

操作流程:
    创建打开a.txt, 向a.txt中写入: 1989 moive 3.14
    1.从a.txt中将内容全部读取到变量中, 输入到屏幕上 
        int 
        字符串 
        double 
    2.将三个变量中的内容输出到b.txt文件中
        i str d - fprintf 

非格式化的输入输出
    依次将c.txt中的内容读取, 写入到d.txt文件中 


文件读写位置 
    //1    2    3    4    5    6    7    8
    //---- ---- ---- ---- ---- ---- ---- ---- ---- 
    //                                        ^
    fwrite(a, sizeof(int), len, fp);

    rewind(fp);
    //1    2    3    4    5    6    7    8
    //---- ---- ---- ---- ---- ---- ---- ---- ---- 
    //^
    
    fread(b, sizeof(int), 9, fp);

    fopen("c.txt", "a");
I/O流的打开

include <stdio.h>
FILE* fopen(
       const char* path,//文件路径
       const char* mode //打开模式
);

成功返回I/O流指针,作为后续I/O流函数的参数,失败返回NULL
之前的所有的代码 - 掉电丢失  - 存储在内存 
运行的过程中出现的数据 - 存储到闪存 - 掉电数据不丢失 

如果将数据存储到某个文件中 - 存储到磁盘中 
如果程序从某个文件中读取数据 - 从磁盘中读取数据 
    对文件的操作 
    
标准C库中 - 

fopen - 打开一个文件 
    向文件中写入数据
    从文件中读取数据 
    前提 - 打开文件 

fopen("./a.txt", "r");

fclose - 关闭一个文件 
    将文件关闭

    fp = fopen("b.txt", "r");//只读方式,存在读取,不存在失败
    if(fp){
        printf("open success\n");
    }
    printf("open failed\n");

打开模式

在这里插入图片描述

struct _iobuf{
	char *_ptr; //文件输入的下一个位置
	int _cnt; //当前缓冲区的相对位置
	char *_base;//指基础位置(文件的起始位置)
	int _flag; //文件标志
	int _file;//文件的有效性验证
	int _charbuf;//检查缓冲区状况
	int _bufsiz;//文件的大小
	char *_tmpfname;//临时文件名
};
typedef struct _iobuf FILE;
stdio.h - 声明
I/O流的关闭

int fclose(
	FILE* fp // I/O流指针
);

成功返回0,失败返回EOF
//文件操作的标准C库函数
#include <stdio.h>

int main(void){
	FILE* fp = NULL;
	//fp = fopen("a.txt", "w");//只写方式,存在清空,不存在创建新文件
	fp = fopen("b.txt", "r");//只读方式,存在读取,不存在失败
	if(fp == NULL){
		printf("open failed\n");
		return -1;
	}
	printf("open sunccess\n");

	//关闭文件
	fclose(fp);
	fp = NULL;//好习惯

	return 0;
}
tarena@TNV:day21$ vim file.c
tarena@TNV:day21$ gcc file.c -o file
tarena@TNV:day21$ ./file
open sunccess
tarena@TNV:day21$ vim file.c
tarena@TNV:day21$ gcc file.c -o file
tarena@TNV:day21$ ./file
open failed

系统每个进程缺省打开三个标准I/O流

在这里插入图片描述

格式化输出

在这里插入图片描述

格式化输入

在这里插入图片描述

#include <stdio.h>

int main(void){
	FILE* fp = NULL;
	//打开文件
	fp = fopen("a.txt", "r");//以只读方式打开当前目录下的a.txt

	if(NULL == fp){
		printf("file open failed\n");
		return -1;
	}

	//定义变量存储从文件中读取的内容
	int i = 0;
	char str[20] = {0};
	double d = 0;
	//按照一定的格式从a.txt文件中读取数据保存到变量i, str, d中
	fscanf(fp, "%d%s%lg", &i, str, &d);
	printf("i = %d, str[20] = %s, d = %lg\n", i, str, d);
	
	//关闭文件
	fclose(fp);
	fp = NULL;
	
	FILE* fpw = fopen("b.txt", "w+");//可读可写方式打开b.txt
	//按照指定的格式将变量i , str, d中的数据保存到文件b.txt中
	fprintf(fpw, "%d %s %lg", i, str, d);

	//关闭文件
	fclose(fpw);
	fpw = NULL;

    //从标准输入设备键盘上获取数据保存到i,str,d中
    fscanf(stdin, "%d%s%lg", &i, str, &d);
    //将i,str,d打印输出到标准输出设备显示器上
    fprintf(stdout, "%d,%s,%lg\n", i, str, d);
    //将i,str,d打印输出到标准输出设备显示器上
    fprintf(stderr, "%d,%s,%lg\n", i, str, d);

	return 0;
}
tarena@TNV:day22$ vim file.c
tarena@TNV:day22$ gcc file.c -o file
tarena@TNV:day22$ ./file
i = 1989, str[20] = movie, d = 3.14
2022 sunny 5.67
2022 sunny 5.67
2022 sunny 5.67
tarena@TNV:day22$ cat a.txt
1989 movie 3.14
tarena@TNV:day22$ cat b.txt
1989 movie 3.14

fgetc 和 fputc - 格式化输入输出

在这里插入图片描述

在这里插入图片描述

//实验步骤:
//touch c.txt
//echo 1233456789abcdef > c.txt
#include <stdio.h>

int main(void){
	FILE* fpr = fopen("c.txt", "r");
	if(NULL == fpr){
		printf("file open failed\n");
		return -1;
	}
	FILE* fpw = fopen("d.txt", "w+");
	if(NULL == fpw){
		printf("file open failed\n");
		return -1;
	}

	int ch;
	while(1){
		ch = fgetc(fpr);//从c.txt文件中读取一个字符保存到ch变量中
		if(EOF == ch)//判断遇到了错误,或者到了文件末尾
			break;
		fputc(ch, fpw);//向d.txt文件中写入一个字符
	}

	//关闭文件
	fclose(fpr);
	fpr = NULL;
	fclose(fpw);
	fpw = NULL;

	return 0;
}
tarena@TNV:day22$ ls
a.txt  b.txt  file  file.c
tarena@TNV:day22$ touch c.txt
tarena@TNV:day22$ echo 1233456789abcdef > c.txt
tarena@TNV:day22$ cat c.txt
1233456789abcdef
tarena@TNV:day22$ vim File2.c
tarena@TNV:day22$ gcc File2.c -o File2
tarena@TNV:day22$ ./File2
tarena@TNV:day22$ ls
a.txt  b.txt  c.txt  d.txt  file  File2  File2.c  file.c
tarena@TNV:day22$ cat d.txt
1233456789abcdef

fputs 和 fgets - 非格式化输入输出

在这里插入图片描述

在这里插入图片描述

//实验步骤:
//touch e.txt
//echo 1233456789abcdef > e.txt
#include <stdio.h>

int main(void){
	FILE* fpr = fopen("e.txt", "r");
	if(NULL == fpr){
		printf("file open failed\n");
		return -1;
	}
	FILE* fpw = fopen("f.txt", "w+");
	if(NULL == fpw){
		printf("file open failed\n");
		return -1;
	}

	//fgets和fputs
	char str[4] = {0};//暂存到文件中读取的字符串
	while(fgets(str, 4, fpr) != NULL){//循环从文件e.txt中读取3个字符
		fputs(str, fpw);//将读取到的数据写入到文件f.txt中
	}

	//关闭文件
	fclose(fpr);
	fpr = NULL;
	fclose(fpw);
	fpw = NULL;

	return 0;
}
tarena@TNV:day22$ ls
a.txt  b.txt  c.txt  d.txt  file  File2  File2.c  file3  file3.c  file.c
tarena@TNV:day22$ touch e.txt
tarena@TNV:day22$ echo 1233456789abcdef > e.txt
tarena@TNV:day22$ gcc file3.c -o file3
tarena@TNV:day22$ ./file3
tarena@TNV:day22$ ls
a.txt  b.txt  c.txt  d.txt  e.txt  file  File2  File2.c  file3  file3.c  file.c  f.txt
tarena@TNV:day22$ cat f.txt
1233456789abcdef
tarena@TNV:day22$ cat e.txt
1233456789abcdef

二进制I/O – fwrite和fread

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>

int main(void){
	FILE* fpw = fopen("m.txt", "w+");
	if(NULL == fpw){
		printf("file open failed\n");
		return -1;
	}

	int a[] = {1,2,3,4,5,6,7,8,9,10};
	int len = sizeof(a) / sizeof(a[0]);
	int size;
	//向文件m.txt中写入len个数据,每个数据4字节
	size = fwrite(a, sizeof(int), len, fpw);
	printf("实际写了%d个数据\n", size);

	//从文件m.txt中读取数据
	int b[8] = {0};//定义数组存储文件m.txt中读取到的内容
	size = fread(b, sizeof(int), 8, fpw);//想要从文件m.txt中读取到9个int类型数据存储到数组b中
	printf("实际读取了%d个数据\n", size);

	//关闭文件
	fclose(fpw);
	fpw = NULL;

	return 0;
}
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ ls
a.txt  b.txt  c.txt  d.txt  e.txt  file  File2  File2.c  file3  file3.c  file4.c  file.c  f.txt
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了10个数据

tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了10个数据
实际写了0个数据
#include <stdio.h>

int main(void){
	FILE* fpw = fopen("m.txt", "w+");
	if(NULL == fpw){
		printf("file open failed\n");
		return -1;
	}

	int a[] = {1,2,3,4,5,6,7,8,9,10};
	int len = sizeof(a) / sizeof(a[0]);
	int size;
	//向文件m.txt中写入len个数据,每个数据4字节
	size = fwrite(a, sizeof(int), len, fpw);
	printf("实际写了%d个数据\n", size);

	rewind(fpw);
	
	//从文件m.txt中读取数据
	int b[8] = {0};//定义数组存储文件m.txt中读取到的内容
	size = fread(b, sizeof(int), 9, fpw);//想要从文件m.txt中读取到9个int类型数据存储到数组b中
	printf("实际读取了%d个数据\n", size);

	for(int i = 0; i < size; i++){
		printf("%d ",b[i]);
	}
	printf("\n");

	//关闭文件
	fclose(fpw);
	fpw = NULL;

	return 0;
}
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ 
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了10个数据
实际读取了9个数据
1 2 3 4 5 6 7 8 9

文件位置与随机访问 – fseek和ftell

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>

int main(void){
	FILE* fpw = fopen("m.txt", "w+");
	if(NULL == fpw){
		printf("file open failed\n");
		return -1;
	}

	int a[] = {1,2,3,4,5,6,7,8};
	int len = sizeof(a) / sizeof(a[0]);
	int size;
	//向文件m.txt中写入len个数据,每个数据4字节
	size = fwrite(a, sizeof(int), len, fpw);
	printf("实际写了%d个数据\n", size);

	rewind(fpw);

	//从文件m.txt中读取数据
	int b[8] = {0};//定义数组存储文件m.txt中读取到的内容
	size = fread(b, sizeof(int), 8, fpw);//想要从文件m.txt中读取到8个int类型数据存储到数组b中
	printf("实际读取了%d个数据\n", size);

	for(int i = 0; i < size; i++){
		printf("%d ",b[i]);
	}
	printf("\n");



	//fseek函数演示
	int c[2] = {0};
	fseek(fpw, 8, SEEK_SET);//从文件头开始往后数8个字节的位置开始操作文件
	//1		2	  3		4	  5		6	  7		8
	//----  ----  ----  ----  ----  ----  ----  ----
	//            ^
	
	fread(c, sizeof(int), 2, fpw);
	printf("%d, %d\n", c[0], c[1]);
	//1		2	  3		4	  5		6	  7		8
	//----  ----  ----  ----  ----  ----  ----  ----
	//                        ^
	//读取完毕后,文件位置调整到了5的位置上

	fseek(fpw, 8, SEEK_CUR);//从当前文件位置向后移动8个字节的位置开始文件操作
	//1		2	  3		4	  5		6	  7		8
	//----  ----  ----  ----  ----  ----  ----  ----
	//                                    ^

	fread(c, sizeof(int), 2, fpw);
	printf("%d, %d\n", c[0], c[1]);
	//1		2	  3		4	  5		6	  7		8
	//----  ----  ----  ----  ----  ----  ----  ----  ----
	//                                                ^
	//读取完毕后,文件位置调整到了文件末尾

	fseek(fpw, -12, SEEK_END);//从文件末尾向前调整12个字节的位置开始操作文件
	//1		2	  3		4	  5		6	  7		8
	//----  ----  ----  ----  ----  ----  ----  ----  ----
	//                              ^
	fread(c, sizeof(int), 2, fpw);
	printf("%d, %d\n", c[0], c[1]);
	//1		2	  3		4	  5		6	  7		8
	//----  ----  ----  ----  ----  ----  ----  ----  ----
	//                                          ^

	rewind(fpw);
	int ch[10] = {0};
	size = fread(ch, sizeof(int), 10, fpw);
	printf("实际读取了%d个字节\n", size);
	for(int i = 0; i < size; i++){
		printf("%d ", ch[i]);
	}
	printf("\n");

	printf("当前的读写位置为:%ld\n", ftell(fpw));

	//关闭文件
	fclose(fpw);
	fpw = NULL;

	return 0;
}

tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了8个数据
实际读取了8个数据
1 2 3 4 5 6 7 8 
3, 4
7, 8
6, 7
实际读取了8个字节
1 2 3 4 5 6 7 8 
当前的读写位置为:32

endl

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良辰美景好时光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值