C语言--Micro_Frank

Frank的C语言

bilibili up主Micro_Frank的独具风格C语言教学,满满的干货与独特的教学风格
看到比较仓促,所以笔记也较为仓促

第一章:C语言简史与基本元素

  1. 标识符:名字(命名的规则)
  2. 关键字:已经被用过的名字,即程序员不能用来命名

第二章:二进制,变量,整型与浮点型

简单的储存

  1. 计算机系统中都会转化为二进制的 源码,反码,补码 进行存储
  2. 变量

整型

  1. int型详解:在内存中的存储与溢出
  2. printf第一讲:格式化输出的一些特性
//printf第一讲
#include <stdio.h>

int main(void) {
	
	int number = 100;

	//以十进制表示
	printf("Decimal : %d\n", number);

	//以八进制表示
	printf("Octal : %o\n", number);

	//以十六进制表示
	printf("Hexadecimal : 0x%x\n", number);
	printf("Hexadecimal : 0X%X\n", number);

	//-+和数字的使用
	printf("向左靠:%-10d\n", number);
	printf("向右靠:%10d\n", number);
	printf("前导0:%010d\n", number);

	//+可以显示数的正负性
	printf("number1 = %+d\n", 100);
	printf("number2 = %+d\n", -100);

	return 0;
}
  1. unsigned int在index(数组下标索引)情况下的使用
  2. short在空间有限的情况下的使用
  3. unsigned short在坐标中的应用
  4. long与long long
  5. 隐式与显式的类型转换(都用显示可以提高代码的可读性和可修改性)
  6. size_t和sizeof
#include <stdio.h>

int main(void) {

	//%z是sizeof()返回的返回类型,即size_t

	printf("Size of short : %zu Byte(s)\n", sizeof(short));

	printf("Size of int : %zu Byte(s)\n", sizeof(int));

	return 0;
}
  1. 实际开发过程中的<stdint.h>和<inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
	
	int16_t number1 = INT16_MAX;
	printf("number1 = %" PRId16 "\n", number1);

	int32_t number2 = INT32_MAX;
	printf("number2 = %" PRId32 "\n", number2);

	uint16_t number3 = 65535U;
	printf("number3 = %" PRIu16 "\n", number3);

	uint32_t number4 = 4294967295U;
	printf("number4 = %" PRIu32 "\n", number4);

	return 0;
}
  1. least与fast整型用来限制变量的范围(了解)

浮点数

  1. 浮点数的存储原理(理解)
  2. float(速度更快,3D图形渲染)与double(精度更准,)
#include <stdio.h>
#include <float.h>

int main(void) {

	float float_num = 1.0 / 3.0;
	double double_num = 1.0 / 3.0;

//隐藏1位
	printf("Float precision : %.20f\n", float_num);
	printf("Double precision : %.20lf\n", double_num);

	printf("Defined max precision for float : %d\n", FLT_DIG);
	printf("Defined max precision for double : %d\n", DBL_DIG);


	return 0;
}
float temperature = 36.5f;
printf("temperature = %.2f\n", temperature);
  1. 浮点数的精度丢失和科学计数法
#include <stdio.h>

int main(void) {

	float number1 = 1.232f;

	printf("number1 %%f : %f\n", number1);
	printf("number1 = %.3f\n", number1);
	printf("number1 %%a = %a\n", number1);
	printf("number1 %%A = %A\n", number1);


	float number2 = 123.45;

	printf("number2 %%e : %.5e\n", number2);

	
	float number3 = 1.234e+2;

	printf("number3 %%f = %.1f\n", number3);


	return 0;
}
  1. 浮点数的overflow(上溢)和underflow(下溢):注意理解
#include <stdio.h>
#include <float.h>
#include <math.h>

int main(void) {
	
	//上溢
	float max_float = FLT_MAX;

	float overflow = max_float * 1000.0f;

	//下溢
	float min_float = FLT_MIN;
	
	float underflow = min_float / 1000.0f;


	//上溢的inf和下溢的丢失精度
	printf("Maximum of float : %e\n", max_float);
	printf("Overflow : %e\n", overflow);
	printf("Minimun Positive Float : %e\n", min_float);
	printf("Underflow : %e\n", underflow);

	return 0;
}
  1. Infinity与Nan
  2. 最近偶数舍入(四舍六入五留双)
    拓展:IEEE754隐含位,Decimal

char类型与ASCII 和 bool类型

  1. char
    1. ASCII,Unicode
    2. 有无符号的问题:unsigned char(-128-127) 和 char(0-255)
  2. 转义序列: \n与\r的区别与联系 , 一些特别的转义序列
  3. bool

const 和 define

常量:const
宏定义:define

第三章:运算符与优先级

运算符:
/ + - * / %
/ + -= += *= /= %= ^= &= <<= >>= |= ~= (1. 提高性能, 2. 提高可读性)
/ -- ++
/ << >>(可以提高计算机的计算效率,对待有符号的无符号时注意符号位的变化,企业级应用很复杂)
/ |(按位或,设置特定位组合标志位) &(按位与,可以将一个特定位清零) ^(按位异或,) ~(按位取反)
/ && ||
/ ,(逗号运算符,谨慎使用)

#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>

int main(void) {

	uint32_t a = 1, b = 2, c = 3;

	uint32_t result = (a += 1, b += 2, c += 3);

	printf("a = %" PRIu32 " b = %" PRIu32" c = %" PRIu32 " result = %" PRIu32" \n", a, b, c, result );

	return 0;
}

优先级:详细请见微软C语言官方文档

第四章:选择

  1. if(语句)
//if 语句
if () {

}
else {

}

//if-else if 语句
if () {

}
else if () {

}
else {

}

要避免if语句的嵌套,以提高可读性(解决问题:面对对象语言中的策略模式,但C语言中可以用结构体来进行)
2. switch
if-else 和 switch 从原理上来说,后者更快,但在现代计算机中完全可以忽略这个差异

第五章:循环

scanf(格式化输入)

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {

	uint32_t number;
	char* str;

	//理解三个的区别
	scanf("%d%d%d", &number, &number, &number);
	scanf("%d, %d, %d", &number, &number, &number);
	scanf("%d\n", &number);

	//简单了解scanf_s的用法
	scanf_s("%s", str, 50);

	return 0;
}

三种循环

  1. while
    自动贩卖机案例
    利用死循环与break
  2. do-while
    游戏菜单案例
  3. for
    进度条案例
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <Windows.h>


int main(void) {

	const uint32_t total_steps = 50;

	puts("正在处理:");

	for (uint32_t i = 1; i <= total_steps; i++) {
		
		printf("\r[");

		for (uint32_t j = 0; j < i; j++) {
			printf("#");
		}

		for (uint32_t j = i; j < total_steps; j++) {
			printf(" ");
		}

		printf("] %" PRIu32 " %%", (i * 100) / total_steps);

		fflush(stdout);
		Sleep(50);
	}

	printf("\n处理完成!\n");

	return 0;
}
  1. continue:跳过本次循环的剩余部分

第六章:数组

数组的使用注意事项
数组大小(一维和二维)隐式初始化

五子棋案例
农作物生长案例

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

#define ROWS 10		//农作物的行
#define COLS 20		//农作物的列
#define EMPTY 0		//空地
#define PLANTED 1	//已经种植了
#define MATURE 2	//成熟的作物
#define STONE 3		//石头

void print_farm(int farm[][COLS]);

int main(void) {

	int farm[ROWS][COLS] = { [1] [1] = STONE,[2][2] = STONE };

	srand((unsigned int)time(NULL));

	for (int i = 0; i < ROWS; i++) {

		for (int j = 0; j < COLS; j++) {

			farm[i][j] = (rand() % 2 == 0) ? EMPTY : PLANTED;

		}
	}
	print_farm(farm);
	Sleep(1000);

	for (int time = 0; time < 5; time++) {
		//一共五个时段,每个时段会有%30的作物成熟
		system("cls");

		for (int i = 0; i < ROWS; i++) {
			for (int j = 0; j < COLS; j++) {
				if (farm[i][j] == PLANTED) {
					if (rand() % 10 < 3) {
						farm[i][j] = MATURE;
					}
				}
			}
		}

		print_farm(farm);
		Sleep(1000);
	}


	return 0;
}

void print_farm(int farm[][COLS]) {

	for (int i = 0; i < ROWS; i++) {

		for (int j = 0; j < COLS; j++) {

			switch (farm[i][j])
			{
			case EMPTY:
				printf(".");
				break;
			case PLANTED: 
				printf("*");
				break;
			case MATURE:
				printf("#");
				break;
			case STONE:
				printf("0");
				break;
			default:
				break;
			}
		}
		printf("\n");
	}
}

第七章:函数

function:函数(功能块)
函数的定义和函数的使用,使功能模块化

石头剪刀布案例(软件工程中的规则映射(表驱动法),VS2022的命名改变技巧ctrl+f)

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

#define ROCK 1
#define PAPER 2
#define SCISSORS 3

void print_instructions(void);
uint32_t get_player_choice(void);
uint32_t get_computer_choice(void);
void print_choice(uint32_t choice);
void determine_winner(uint32_t player_choice, uint32_t computer_choice);


int main(void) {

	srand(time(NULL));

	print_instructions();

	uint32_t player_choice = get_player_choice();
	uint32_t computer_choice = get_computer_choice();

	puts("你选择了:");
	print_choice(player_choice);
	puts("电脑选择了:");
	print_choice(computer_choice);

	determine_winner(player_choice, computer_choice);

	return 0;
}


void print_instructions() {
	puts("Game start!");
	puts("1. ROCK    2. PAPER    3.SCISSORS");
	puts("=====================================");
}

uint32_t get_player_choice() {

	uint32_t choice;
	puts("请输入您的选择:");
	scanf_s("%d", &choice);

	while (choice < ROCK || choice > SCISSORS) {
		puts("请重新输入符合规则的选择:");
		scanf_s("%d", &choice);
	}

	return choice;
}

uint32_t get_computer_choice() {

	//// 使用当前时间作为随机数种子
	//srand(time(NULL));

	//// 生成随机数
	//random_number = rand() % (max_range - min_range + 1) + min_range;

	return (rand() % (SCISSORS - ROCK + 1) + ROCK);
}

void print_choice(uint32_t choice) {
	switch (choice) {
	case ROCK:
		puts("石头");
		break;
	case PAPER:
		puts("布");
		break;
	case SCISSORS:
		puts("剪刀");
		break;
	default:
		break;
	}
}

void determine_winner(uint32_t player_choice, uint32_t computer_choice){
	//规则映射

	if (player_choice == computer_choice) {
		puts("平局!");
		return;
	}

	uint32_t winner_moves[4] = { 0, SCISSORS, ROCK, PAPER};//0是占位符
	if (computer_choice == winner_moves[player_choice]) {
		puts("你赢了!");
	}
	else {
		puts("你输了!");
	}
}

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

char get_credits(uint16_t grades);
bool is_leap_year(uint32_t year);
uint32_t get_days(uint32_t year, uint32_t month);

int main(void)
{
	//成绩评分系统
	uint16_t grades;

	puts("Please enter your grades(0 - 100):");
	scanf_s("%" PRIu16 "", &grades);
	printf("%c\n", get_credits(grades));

	//判断月份天数
	uint32_t year;
	uint32_t month;

	puts("Please enter the year and the month:");
	scanf_s("%" PRIu32 "%" PRIu32 "", &year, &month);
	if (is_leap_year(year)) {
		printf("%" PRIu32" is leap year and the days of month %" PRIu32 " is %"PRIu32"", year, month, get_days(year, month));
	}
	else {
		printf("%" PRIu32" isn't leap year and the days of month %" PRIu32 " is %"PRIu32"", year, month, get_days(year, month));
	}
}

char get_credits(uint16_t grades)
{
	//表驱动法判断成绩
	char credits[] = {'F', 'F', 'F', 'F', 'F', 'E', 'D', 'C', 'B', 'A', 'A'};

	return credits[grades / 10];
}

bool is_leap_year(uint32_t year)
{
	return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

uint32_t get_days(uint32_t year, uint32_t month)
{
	//表驱动法判断月份的天数
	uint32_t days[] = {0, 31, is_leap_year(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};//0是占位符

	return days[month];
}

递归:
普通递归

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int32_t factorial(int32_t number);

int main(void)
{
	int32_t number;
	puts("Please enter a number:");
	scanf_s("%" PRId32 "", &number);

	printf("The factorial of %" PRId32 " is %" PRId32 "\n", number, factorial(number));

	return 0;
}

int32_t factorial(int32_t number)
{
	if (number == 0) {
		return 1;
	}
	else {
		return number * factorial(number - 1);
	}
}

尾递归(编译原理,防止栈溢出等才用)

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int32_t factorial(int32_t number, int32_t acc);

int main(void)
{
	int32_t number;
	puts("Please enter a number:");
	scanf_s("%" PRId32 "", &number);

	printf("The factorial of %" PRId32 " is %" PRId32 "\n", number, factorial(number, 1));

	return 0;
}

int32_t factorial(int32_t number, int32_t acc)
{
	if (number == 0) 
	{
		return acc;
	}
	else
	{
		return factorial(number - 1, number * acc);
	}
}

企业中使用迭代方法代替递归

作用域:一个函数类,一个文件内,整个程序内
静态局部变量在函数内部声明,并且在程序的期间只初始化一次。

多文件中的关键字:extern(如果为static类型,则只能在当前文件下使用 )

寄存器关键字 register (不能使用&)

函数的注释

/**
*Calulate the sum of two intergers.
*
*Parameter:
* - num1: The first integer for the sum.
* - num2: The second integer for the sum.
*
*Returns:
* - The sum of num1 and num2 as an int32_t.
*
*Notes:
* - The function uses int32_t to ensure the calculations is compatiable across difference platforms
* - This is a basic example intended for beginner who are not yet familiar with pointer
*
*Name:
* - lijalen
* - email:lijalen23@outlook.com
*/
int32_t sum(int32_t num1, int32_t num2)
{
	return num1 + num2;s
}

第八章:指针

指针的基本知识

一个非常形象的案例:快捷方式
野指针
空指针

指针的运算

多维数组与指针(数组指针)

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void)
{
	uint32_t matrix[2][3] = {
		{1, 2, 3},
		{4, 5, 6}
	};//定义了一个2行3列的矩阵

	uint32_t(*ptr)[3] = matrix;
	//(*ptr)[3]:ptr是一个指针,它指向包含3个int型的一维数组指针

	//注意和uint32_t* ptr[3]的区别:这是一个可以包含三个指针的指针数组

	for (uint32_t i = 0; i < 2; ++i)
	{
		for (uint32_t j = 0; j < 3; ++j)
		{
			printf("%" PRIu32 " ", ptr[i][j]);
		}
		printf("\n");
	}

	
	return 0;
}

指针数组(存放指针的数组)

函数的值传递和指针传递

第九章:结构体

结构体的基本知识

结构体是一种数据类型

#include <stdio.h>
#include <stdbool.h>

struct Date
{
	int year;
	int month;
	int day;
};

typedef struct Person
{
	char* name;
	bool gender;
	unsigned short age;
} Person;

void print_date(struct Date* date);
void print_person(Person* person);

int main(void)
{
	struct Date today = {2024, 5, 11};
	
	Person lijalen = {"lijalen", true, 18};

	print_date(&today);
	print_person(&lijalen);

	return 0;
}

void print_date(struct Date* date)
{
	printf("Today is %d.%d.%d\n", date->year, date->month, date->day);
}

void print_person(Person* person)
{
	if (person->gender) {
		printf("I am %s and I am a %u years old boy\n", person->name, person->age);
	}
	else {
		printf("I am %s and I am a %u years old girl\n", person->name, person->age);
	}
}

初始化时可以参考Java中的构造函数的思想

结构体其他

  1. 与函数组合
  2. 与数组组合
  3. 嵌套结构体

枚举(enum)

#include <stdio.h>


typedef enum
{
	MONDAY,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY
}Weekday;


int main(void)
{
	Weekday day = FRIDAY;

	printf("%d", day);

	return 0;
}

联合体(union)

它允许在相同的内存位置内储存不同的数据类型。
联合体的所有成员共享一块内存空间,大小等于其最大成员的大小
这意味着在任意时刻,联合体只能储存一个成员的值
一个变量可能储存多种类型的数据,但是在一个给定的时刻内,只是用一种数据类型

运用起来就和面向对象语言中的泛型思想类似

#include <stdio.h>

typedef union
{
	int int_type;
	float float_type;
	char* string_type;

} Data;

typedef enum
{
	INT,
	FLOAT,
	STRING

} DataType;

typedef struct
{
	Data data;
	DataType type;

} TypedData;

void print(TypedData* typed_data);

int main(void)
{
	TypedData data1 = { {.int_type = 10}, INT };
	TypedData data2 = { {.float_type = 3.14f}, FLOAT };
	TypedData data3 = { {.string_type = "Hello world!"}, STRING };

	print(&data1);
	print(&data2);
	print(&data3);

	return 0;
}

void print(TypedData* typed_data)
{
	switch (typed_data->type)
	{
	case INT:
		printf("Integer:%d\n", typed_data->data.int_type);
		break;
	case FLOAT:
		printf("Float:%.2f\n", typed_data->data.float_type);
		break;
	case STRING:
		printf("String:%s\n", typed_data->data.string_type);
		break;
	default:
		break;
	}
}

多文件编程

文字RPG游戏案例


第十章:字符串(string.h)

如何创建一个字符串

  1. 用数组创建
  2. 用指针创建(不可修改)

string.h

  1. strcpy:将一个字符串复制到另一个字符串
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[] = "hello";
	char str2[] = "world";
	char str3[50];

	strcpy_s(str3, sizeof(str3), str1);//微软提高了安全性
	puts(str3);

	strcpy(str3, str2);//原始的形式
	puts(str3);

	return 0;
}
  1. strlen:(size_t)计算\0前的字符串长度
  2. strcat:截取一个字符串到另一个字符串的末尾
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[50] = "Hello";
	const char* str2 = ",world!";
	const char* str3 = "Hello,lijalen!";

	puts(str1);
	
	strcat(str1, str2);//strcat
	puts(str1);

	strcat_s(str1, (int)50 - strlen(str1), str3);//微软的strcat_s
	puts(str1);

	return 0;
}
  1. sprintf_s:将格式化输出转化成字符串,检查了才能输出
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>


int main(void)
{
	char buffer1[50];
	char buffer2[50];
	int number1 = 23;
	float number2 = 3.14f;

	sprintf(buffer1, "The number1 = %d, the number2 = %.2f", number1, number2);
	puts(buffer1);

	int ret = sprintf_s(buffer2, sizeof(buffer2), "The number1 = %d, the number2 = %.2f", number1, number2);
	if (ret = 1)
		puts(buffer2);
	else
		puts("error!");

	return 0;
}
  1. strncpy_s:截取特定长度的字符串到另一个字符串末尾('\0'占一个位置)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[50] = { 0 };
	char str2[] = "Hello world!";
	char str3[50] = { 0 };

	strncpy(str1, str2, 10);
	puts(str1);

	strncpy_s(str3, sizeof(str3), str2, 10);
	puts(str3);


	return 0;
}
  1. gets_s()获得字符串 和 puts()输出字符串
#include <stdio.h>
#include <string.h>

int main(void)
{
	char* str1 = "Hello, lijalen!";
	puts(str1);

	char str2[100] = { 0 };

	gets_s(str2, sizeof(str2));
	puts(str2);

	return 0;
}
  1. strtok_s:按照要求拆分字符串
  2. strcmp:将两个字符串进行比较
  3. strncmp:将两个字符串的前n位进行比较
  4. strchr 和 strrchr:前者是第一次出现字符的位置,后者是最后一一次出现字符的位置
  5. strstr:查找子字符串
  6. strspn 和 strcspn:前者是从开始比较两个字符串到第一个不同字符串出现之前的长度,后者是比较出现相同字符出现之前的长度

第十一章:输入输出与文件操作

流的概念

input stream,output stream,buffer
输入流的数据首先被暂存到一个叫做缓冲区的内存区域

流(stream)

  1. 文件流
    1. 磁盘:用于读取和写入在磁盘上的文件
  2. 标准IO流
    1. stdin:默认连接到键盘,用于程序输入
    2. stdout:默认连接到控制台或者是屏幕,用与程序输出
    3. stderr:默认也连接到控制台或者屏幕,专门输出错误信息和警告,使得其能被区分开来或者是定向到不同的目的地
  3. 管道流
    1. 用于进程之间的通信(IPC),允许一个进程的输出成为另一个的进程输入popen();
  4. 内存流
    1. 允许你将流与内存缓冲区关联,使得你可以向内存中读写数据,就像操作文件一样,POSIX->fmemopen
  5. 网络流
    1. 套接字(sockets)
  6. 设备流
    1. 特殊机器:打印机

scanf 和 scanf_s

文件流

  1. 打开文件和读取文件信息 fopen(),fopen_s(),EOF,FILE,rewind(),memset(),fclose(),'r'
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{

	FILE* file_stream1 = NULL;
	FILE* file_stream2 = NULL;

	char buffer[256];


	file_stream1 = fopen("C:\\Users\\JORDAN\\Desktop\\test1.txt", "r");
	int err = fopen_s(&file_stream2, "C:\\Users\\JORDAN\\Desktop\\test2.txt", "r");

	if (err != 0 || file_stream2 == NULL)
	{
		printf("打开文件失败!\n");
		return EXIT_FAILURE;
	}


	if (file_stream1 == NULL)
	{
		puts("文件打开失败!\n");
		return -1;
	}

	while (fgets(buffer, sizeof(buffer), file_stream1) != NULL)
	{
		printf("%s", buffer);
	}
	printf("\n");
	
	memset(buffer, 0, sizeof(buffer));
	rewind(file_stream1);

	char ch;
	while ((ch = fgetc(file_stream1)) != EOF)
	{
		putchar(ch);
	}


	while ((ch = fgetc(file_stream2)) != EOF) 
	{
		putchar(ch);
	}


	if (fclose(file_stream1) != 0)
	{
		puts("文件关闭失败!\n");
		return EXIT_FAILURE;
	}

	if (fclose(file_stream2) != 0)
	{
		puts("文件关闭失败!\n");
		return EXIT_FAILURE;
	}


	return 0;
}
  1. 写入文件 fputc(),fputs(),fprintf(),'w'
#define _CRT_SECURE_NO_WARNINGS

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

int main(void)
{
	FILE* file_stream = NULL;
	file_stream = fopen("C:\\Users\\JORDAN\\Desktop\\test2.txt", "w");

	if (file_stream == NULL)
	{
		perror("打开文件失败!\n");
		return EXIT_FAILURE;
	}

	fputc('A', file_stream);
	fputc('\n', file_stream);

	fputs("Hello,world!\n", file_stream);

	fprintf(file_stream, "number1 = %d, number2 = %.3f, string :  %s", 34, 11.234, "lijalen");


	if (fclose(file_stream) != 0)
	{
		perror("关闭文件失败!\n");
	}
	printf("写入文件成功!\n");
;
	return 0;
}
  1. ftell(),fseek(),rewind()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(void)
{
	FILE* file_stream = fopen("C:\\Users\\JORDAN\\Desktop\\test2.txt", "r");

	if (file_stream == NULL)
	{
		perror("文件打开失败!\n");
		return EXIT_FAILURE;
	}

	long position = ftell(file_stream);
	printf("当前位置:%ld\n", position);

	char buffer[100];
	fgets(buffer, sizeof(buffer), file_stream);
	printf("读取的字符串:%s", buffer);

	position = ftell(file_stream);
	printf("新的位置:%ld\n", position);


	fseek(file_stream, 0, SEEK_SET);
	position = ftell(file_stream);
	printf("使用fseek之后的当前位置:%d\n", position);


	rewind(file_stream);
	position = ftell(file_stream);
	printf("使用rewind后的当前位置:%d\n", position);

	fclose(file_stream);

	return 0;
}
  1. fscanf(),fprintf()
#define _CRT_SECURE_NO_WARNINGS

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

int main(void)
{
	FILE* file_stream = fopen("C:\\Users\\JORDAN\\Desktop\\test2.txt", "r");

	if (file_stream == NULL)
	{
		perror("文件打开失败!\n");
		return EXIT_FAILURE;
	}

	char buffer[100];
	fscanf(file_stream, "%s", buffer);
	puts(buffer);

	fclose(file_stream);

	return 0;
}
  1. ferror(),feof(),clearerr()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
	FILE* file_stream = fopen("C:\\Users\\JORDAN\\Desktop\\test2.txt", "r");

	if (file_stream == NULL)
	{
		perror("文件打开失败!\n");
		return EXIT_FAILURE;
	}

	char buffer[100];

	while (fgets(buffer, sizeof(buffer), file_stream) != NULL)
	{
		puts(buffer);
	}

	if (ferror(file_stream))
	{
		perror("读取文件出错!\n");
		clearerr(file_stream);
	}

	if (feof(file_stream))
	{
		printf("文件已到达末尾!\n");
	}
	else
	{
		printf("文件未到达末尾,可能因为发生了错误!\n");
	}

	fclose(file_stream);

	return 0;
}

第十二章:头文件里的函数

math.h

time.h

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h>


int main(void)
{
	time_t now = time(NULL);

	printf("当前时间戳为:%td\n", now);


	struct tm local_time;
	local_time = *(localtime(&now));
	//localtime_s(&local_time, &now);

	struct tm utc_time;
	utc_time = *(gmtime(&now));
	//gmtime_s(&utc_time, &now);

	char local_time_str[80];
	char utc_time_str[80];
	strftime(local_time_str, sizeof(local_time_str),"%Y-%m-%d %H:%M:%S", &local_time);
	strftime(utc_time_str, sizeof(utc_time_str), "%Y-%m-%d %H:%M:%S", &utc_time);

	printf("本地时间:%s\n", local_time_str);
	printf("UTC时间:%s\n", utc_time_str);

	return 0;
}

errno.h

第十三章:动态内存管理

栈内存(Stack Memory)

  1. 自动管理机制:函数调用时候,局部变量会分配在栈上,当函数返回时,局部变量全部销毁释放
  2. 速度快:栈内存分配和访问速度通常比堆内存快,它是一种线性数据结构
  3. 大小有限制,栈的大小,在程序启动的那一刹那就意味p着已经确定了,如果栈内存空间耗尽,就意味着崩溃,栈溢出
  4. 函数的局部变量,函数参数,函数调用的返回地址

堆内存(Heap Memory)

  1. 动态管理:malloc,calloc,realloc,free
  2. 速度相对于栈空间慢,它需要在内存中寻找足够大的连续空间块
  3. 大小灵活:堆的大小通常可用系统内存限制,而非栈本身的限制。能够按需分配

malloc, realloc, calloc, free

malloc:在内存中开辟一块新的空间,并返回的一个void* 指针(用于泛型编程),注意内存的手动释放
realloc:在原空间的基础上增加开辟的空间
calloc:不仅可以分配内存,还可以将所有位置的初始值设置为0;
free:释放开辟的空间

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


typedef struct Employee
{
	char* name;
	int task_count;
	int* task_list;
} Employee;

Employee* creat_employee(const char* name, int task_count);

void free_employee(Employee* employee);

int main(void)
{



	return EXIT_SUCCESS;
}

Employee* creat_employee(const char* name, int task_count)
{
	Employee* new_employee = (Employee*)malloc(sizeof(Employee));
	
	if (new_employee == NULL)
	{
		perror("Malloc failed!\n");
		return NULL;
	}

	new_employee->name = (char*)malloc(strlen(name) + 1);

	if (new_employee->name == NULL)
	{
		perror("Malloc failed!\n");
		free(new_employee);
		return NULL;
	}

	strcpy(new_employee->name, name);
	new_employee->task_count = task_count;

	new_employee->task_list = (int*)calloc(task_count, sizeof(int));

	if (new_employee->task_list == NULL)
	{
		perror("Calloc failed!\n");
		free(new_employee->name);
		free(new_employee);

		return NULL;
	}

	return new_employee;
}

void free_employee(Employee* employee)
{
	if (employee != NULL)
	{
		free(employee->name);
		free(employee->task_list);
		free(employee);

	}

}

第十四章:指针拓展

多级指针

模拟一个游戏服务器

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

typedef struct Player
{
	unsigned ID;
	char name[30];

} Player;

void update_player_list(Player** player_list, int* currrent_size, int new_size, Player newplayer);

int main(void)
{
	Player* player_list = NULL;
	int current_size = 0;

	Player new_player1 = { 1, "plyerone" };
	update_player_list(&player_list, &current_size, current_size + 1, new_player1);

	Player new_player2 = { 2, "plyertwo" };
	update_player_list(&player_list, &current_size, current_size + 1, new_player2);

	Player new_player3 = { 3, "plyerthree" };
	update_player_list(&player_list, &current_size, current_size + 1, new_player3);



	return 0;
}

void update_player_list(Player** player_list, int* current_size, int new_size, Player newplayer)
{
	Player* temp = (Player*)realloc(*player_list, sizeof(Player) * new_size);

	if (temp == NULL)
	{
		perror("Realloc failed!\n");
		return;
	}
	else
	{
		*player_list = temp;
		if (*current_size < new_size)
		{
			(*player_list)[*current_size] = newplayer;
			*current_size = new_size;
		}
		return;
	}
}

加上错误日志系统

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#define TIME_STRING_SIZE 26

typedef struct Player
{
	unsigned ID;
	char name[30];

} Player;

void update_player_list(Player** player_list, int* currrent_size, int new_size, Player newplayer);
void print_player_list(const Player* player_list,int current_size);
void log_error(const char* message);
void log_info(const char* message);
void log_message(const char* level, const char* message);

int main(void)
{
	Player* player_list = NULL;
	int current_size = 0;

	Player new_player1 = { 1, "plyerone" };
	update_player_list(&player_list, &current_size, current_size + 1, new_player1);

	Player new_player2 = { 2, "plyertwo" };
	update_player_list(&player_list, &current_size, current_size + 1, new_player2);

	Player new_player3 = { 3, "plyerthree" };
	update_player_list(&player_list, &current_size, current_size + 1, new_player3);

	print_player_list(player_list, current_size);

	return 0;
}

void update_player_list(Player** player_list, int* current_size, int new_size, Player newplayer)
{
	Player* temp = (Player*)realloc(*player_list, sizeof(Player) * new_size);

	if (new_size < *current_size)
	{
		log_error("Error:new_size is smaller than current_size!");
		return;
	}

	if (new_size == *current_size)
	{
		log_info("No resizing needed.The new_size must be greater than current_size to add a new player!");
		return;
	}

	if (temp == NULL)
	{
		log_error("Realloc failed!");
		return;
	}
	else
	{
		*player_list = temp;
		if (*current_size < new_size)
		{
			(*player_list)[*current_size] = newplayer;

		}
		*current_size = new_size;

		log_info("Player added successfully!");
		char infoMessage[100];
		snprintf(infoMessage, sizeof(infoMessage), "Current player count : %d\n", *current_size);
		log_info(infoMessage);
	}
}

void print_player_list(const Player* player_list,int current_size)
{
	for (int i = 0; i < current_size; i++)
	{
		printf("ID:%u       player_name:%s\n", player_list[i].ID, player_list[i].name);
	}
}


void log_error(const char* message)
{
	log_message("ERROR", message);
}

void log_info(const char* message)
{
	log_message("INFO", message);
}

void log_message(const char* level, const char* message)
{
	time_t now = time(NULL);
	char timeStr[TIME_STRING_SIZE];

	if (ctime_s(timeStr, sizeof(timeStr), &now) == 0)
	{
		timeStr[24] = '\0';
		fprintf(stderr, "[%s] %s : %s\n", timeStr, level, message);
	}
	else
	{
		fprintf(stderr, "[ERROR] Failed to get current time\n");
	}
}
```C
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void addString(char*** strArray, int* size, char* str);
void printString(char** strArray, int size);
void freeString(char*** strArray, int size);

int main(void)
{
	char** strArray = NULL;

	int size = 0;

	addString(&strArray, &size, "Hello");
	addString(&strArray, &size, "World");
	addString(&strArray, &size, "!\n");

	printString(strArray, size);

	freeString(&strArray, size);

	return 0;
}

void addString(char*** strArray, int* size, char* str)
{
	char** temp = realloc(*strArray, (*size + 1) * sizeof(char*));
	if (temp == NULL)
	{
		printf("Realloc failed!\n");
		return;
	}
	
	*strArray = temp;

	(*strArray)[*size] = (char*)malloc(strlen(str) + 1);
	if ((*strArray)[*size] != NULL)
	{
		strcpy((*strArray)[*size], str);
		(*size)++;
	}
	else
	{
		fprintf(stderr, "Error!\n");
	}
}

void printString(char** strArray, int size)
{
	for (int i = 0; i <size; i++)
	{
		printf("%s", strArray[i]);
	}
}

void freeString(char*** strArray, int size)
{
	for (int i = 0; i < size; i++)
	{
		free((*strArray)[i]);
	}
	free(*strArray);
}

三级指针的应用(无限追加字符串)

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

void addString(char*** strArray, int* size, char* str);
void printString(char** strArray, int size);
void freeString(char*** strArray, int size);

int main(void)
{
	char** strArray = NULL;

	int size = 0;

	addString(&strArray, &size, "Hello");
	addString(&strArray, &size, "World");
	addString(&strArray, &size, "!\n");

	printString(strArray, size);

	freeString(&strArray, size);

	return 0;
}

void addString(char*** strArray, int* size, char* str)
{
	char** temp = realloc(*strArray, (*size + 1) * sizeof(char*));
	if (temp == NULL)
	{
		printf("Realloc failed!\n");
		return;
	}
	
	*strArray = temp;

	(*strArray)[*size] = (char*)malloc(strlen(str) + 1);
	if ((*strArray)[*size] != NULL)
	{
		strcpy((*strArray)[*size], str);
		(*size)++;
	}
	else
	{
		fprintf(stderr, "Error!\n");
	}
}

void printString(char** strArray, int size)
{
	for (int i = 0; i <size; i++)
	{
		printf("%s", strArray[i]);
	}
}

void freeString(char*** strArray, int size)
{
	for (int i = 0; i < size; i++)
	{
		free((*strArray)[i]);
	}
	free(*strArray);
}

链表的简单指针使用

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

typedef struct Node
{
	int data;
	struct Node* next;
} Node;

void addAtHead(Node** head, int data);
void printList(Node* head);
void freeList(Node** head);


int main(void)
{
	Node* head = NULL;

	addAtHead(&head, 1);
	addAtHead(&head, 2);
	addAtHead(&head, 3);

	printList(head);

	freeList(&head);

	return 0;
}

void addAtHead(Node** head, int data)
{
	Node* newNode = (Node*)malloc(sizeof(Node));

	if (newNode == NULL)
	{
		printf("Malloc failed\n");
		return;
	}

	newNode->data = data;
	newNode->next = *head;
	*head = newNode;
}

void printList(Node* head)
{
	Node* current = head;
	while (current != NULL)
	{
		printf("%d -> ", current->data);
		current = current->next;
	}
	printf("NULL\n");
}

void freeList(Node** head)
{
	Node* temp = NULL;
	while (*head != NULL)
	{
		temp = *head;
		*head = (*head)->next;
		free(temp);
		
	}
}

函数指针

函数指针:指向函数的指针

#include <stdio.h>

int (*myFunctionPoiter)(int, int);

int main(void)
{
	myFunctionPoiter = add;
	
	printf("%d", myFunctionPoiter(23, 45));

	return 0;
}

int add(int num1, int num2)
{
	return (num1 + num2);
}

函数指针的主要作用:实现回调函数
回调函数:允许你将一个函数一个函数作为参数传递给另一个函数,这样,当特定的事件发生时候,可以调用传递的参数(事件驱动编程)

#include <stdio.h>

typedef int(*operation_t)(int num1, int num2);

int add(int num1, int num2);
int substract(int num1, int num2);
int multiply(int num1, int num2);
int divide(int num1, int num2);

int main(void)
{
	operation_t operations[4] = { add, substract, multiply, divide };

	printf("%d\n", operations[0](100, 2))	;
	printf("%d\n", operations[1](100, 2));
	printf("%d\n", operations[2](100, 2));
	printf("%d\n", operations[3](100, 2));

	return 0;
}

int add(int num1, int num2)
{
	return num1 + num2;
}

int substract(int num1, int num2)
{
	return num1 - num2;
}

int multiply(int num1, int num2)
{
	return num1 * num2;
}

int divide(int num1, int num2)
{
	if (num2 == 0)
	{
		printf("除数不能等于0!\n");
		return -1;
	}
	return num1 / num2;
}

回调函数可以高阶地提高代码灵活性和重用性()

#include <stdio.h>

typedef void(*Callback)(int);

void traverse(int* array, int size, Callback callback);
void printElement(int num);
void doubleElement(int num);
void isEven(int num);

int main(void)
{
	int array[] = {1, 2, 3, 4, 5, 6, 7};
	traverse(array, sizeof(array) / sizeof(array[0]), printElement);
	traverse(array, sizeof(array) / sizeof(array[0]), doubleElement);
	traverse(array, sizeof(array) / sizeof(array[0]), isEven);

	return 0;
}

void traverse(int* array, int size, Callback callback)
{
	for (int i = 0; i < size; i++)
	{
		callback(array[i]);
	}
}

void printElement(int num)
{
	printf("%d\n", num);
}

void doubleElement(int num)
{
	printf("%d X 2 = %d\n", num, num * 2);
}

void isEven(int num)
{
	if (num % 2 == 0)
	{
		printf("%d isn't even\n", num);
	}
	else
	{
		printf("%d is even\n", num);
	}
}

函数指针的实际用途:

  1. 事件处理
#include <stdio.h>

typedef enum
{
	PLAYER_ATTACT,
	PLAYER_MOVE,
	EVENT_COUNT
} GameEventType;

typedef void (*EventHandler)(const char* playerName);

EventHandler eventHandlers[EVENT_COUNT];

void registerEventHandler(GameEventType eventType, EventHandler handler);
void dispatchEvent(GameEventType eventType, const char* playerName);	//相当于所有事件的发生就可以用这个东西调用指定函数
void handlePlayerAttact(const char* playerName);
void handlePlayerMove(const char* playerName);

int main(void)
{
	registerEventHandler(PLAYER_ATTACT, handlePlayerAttact);
	registerEventHandler(PLAYER_MOVE, handlePlayerMove);

	dispatchEvent(PLAYER_ATTACT, "lijalen");
	dispatchEvent(PLAYER_MOVE, "liajalen");

	return 0;
}

void registerEventHandler(GameEventType eventType, EventHandler handler)
{
	if (eventType >= 0 && eventType < EVENT_COUNT)
	{
		eventHandlers[eventType] = handler;
	}
}

void dispatchEvent(GameEventType eventType, const char* playerName)
{
	if (eventHandlers[eventType] != NULL)
	{
		eventHandlers[eventType](playerName);
	}
}

void handlePlayerAttact(const char* playerName)
{
	printf("%s:Attact!\n", playerName);
}

void handlePlayerMove(const char* playerName)
{
	printf("%s:Move!\n", playerName);
}

  1. 异步编程
  2. 定时器函数
  3. 插件架构中的扩展点定义
  4. 排序算法中的自定义比较函数
  5. 多线程或并发编程中的线程启动函数
  6. 用户界面组件中的自定义渲染策略
  7. 网络请求处理中的协议解析器
  8. 状态机实现中的状态转换触发器
  9. 资源管理器中的资源释放策略回调

指针的生命周期

第十五章:杂知识点

多文件编程

  1. 多文件编程:头文件与编译
    1. 头文件:
      1. 组织性
      2. 重用性
      3. 编译效率
      4. 避免重复包含
    2. 函数声明与实现
      1. 在一个头文件(.h)中声明
      2. 在一个源文件(.c)中实现
  2. 预处理指令

泛型编程

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


int compare_value(const void* a, const void* b);

void generic_sort(void* array, size_t element_size, size_t element_count, int (*compare)(const void* a, const void* b));

int main(void)
{
	int arr[] = { 12,1343,-234, 115465, -12233,-34 };

	size_t arr_count = sizeof(arr) / sizeof(arr[0]);

	generic_sort(arr, sizeof(arr[0]), arr_count, compare_value);

	for (size_t i = 0; i < arr_count; i++)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");

	return 0;
}

int compare_value(const void* a, const void* b)
{
	int arg1 = *(const int*)a;
	int arg2 = *(const int*)b;

	if (arg1 < arg2)return -1;
	if (arg1 > arg2)return 1;	
	return 0;
}

void generic_sort(void* array, size_t element_size, size_t element_count, int(*compare)(const void* a, const void* b))
{
	qsort(array, element_count, element_size, compare);
}


实际案例汇总

C语言进阶

Frank-C语言学习后,再寻找C语言的奇淫技巧。

posted @ 2024-06-06 21:47  lijalen  阅读(760)  评论(0)    收藏  举报