C语言--Micro_Frank
Frank的C语言
bilibili up主Micro_Frank的独具风格C语言教学,满满的干货与独特的教学风格
看到比较仓促,所以笔记也较为仓促
第一章:C语言简史与基本元素
- 标识符:名字(命名的规则)
- 关键字:已经被用过的名字,即程序员不能用来命名
第二章:二进制,变量,整型与浮点型
简单的储存
- 计算机系统中都会转化为二进制的 源码,反码,补码 进行存储
- 变量
整型
- int型详解:在内存中的存储与溢出
- 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;
}
- unsigned int在index(数组下标索引)情况下的使用
- short在空间有限的情况下的使用
- unsigned short在坐标中的应用
- long与long long
- 隐式与显式的类型转换(都用显示可以提高代码的可读性和可修改性)
- 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;
}
- 实际开发过程中的<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;
}
- least与fast整型用来限制变量的范围(了解)
浮点数
- 浮点数的存储原理(理解)
- 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);
- 浮点数的精度丢失和科学计数法
#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;
}
- 浮点数的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;
}
- Infinity与Nan
- 最近偶数舍入(四舍六入五留双)
拓展:IEEE754隐含位,Decimal
char类型与ASCII 和 bool类型
- char
- ASCII,Unicode
- 有无符号的问题:unsigned char(-128-127) 和 char(0-255)
- 转义序列: \n与\r的区别与联系 , 一些特别的转义序列
- 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语言官方文档
第四章:选择
- 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;
}
三种循环
- while
自动贩卖机案例
利用死循环与break - do-while
游戏菜单案例 - 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;
}
- 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中的构造函数的思想
结构体其他
- 与函数组合
- 与数组组合
- 嵌套结构体
枚举(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)
如何创建一个字符串
- 用数组创建
- 用指针创建(不可修改)
string.h
- 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;
}
- strlen:(size_t)计算\0前的字符串长度
- 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;
}
- 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;
}
- 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;
}
- 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;
}
- strtok_s:按照要求拆分字符串
- strcmp:将两个字符串进行比较
- strncmp:将两个字符串的前n位进行比较
- strchr 和 strrchr:前者是第一次出现字符的位置,后者是最后一一次出现字符的位置
- strstr:查找子字符串
- strspn 和 strcspn:前者是从开始比较两个字符串到第一个不同字符串出现之前的长度,后者是比较出现相同字符出现之前的长度
第十一章:输入输出与文件操作
流的概念
input stream,output stream,buffer
输入流的数据首先被暂存到一个叫做缓冲区的内存区域
流(stream)
- 文件流
- 磁盘:用于读取和写入在磁盘上的文件
- 标准IO流
- stdin:默认连接到键盘,用于程序输入
- stdout:默认连接到控制台或者是屏幕,用与程序输出
- stderr:默认也连接到控制台或者屏幕,专门输出错误信息和警告,使得其能被区分开来或者是定向到不同的目的地
- 管道流
- 用于进程之间的通信(IPC),允许一个进程的输出成为另一个的进程输入popen();
- 内存流
- 允许你将流与内存缓冲区关联,使得你可以向内存中读写数据,就像操作文件一样,POSIX->fmemopen
- 网络流
- 套接字(sockets)
- 设备流
- 特殊机器:打印机
scanf 和 scanf_s
文件流
- 打开文件和读取文件信息 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;
}
- 写入文件 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;
}
- 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;
}
- 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;
}
- 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)
- 自动管理机制:函数调用时候,局部变量会分配在栈上,当函数返回时,局部变量全部销毁释放
- 速度快:栈内存分配和访问速度通常比堆内存快,它是一种线性数据结构
- 大小有限制,栈的大小,在程序启动的那一刹那就意味p着已经确定了,如果栈内存空间耗尽,就意味着崩溃,栈溢出
- 函数的局部变量,函数参数,函数调用的返回地址
堆内存(Heap Memory)
- 动态管理:malloc,calloc,realloc,free
- 速度相对于栈空间慢,它需要在内存中寻找足够大的连续空间块
- 大小灵活:堆的大小通常可用系统内存限制,而非栈本身的限制。能够按需分配
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, ¤t_size, current_size + 1, new_player1);
Player new_player2 = { 2, "plyertwo" };
update_player_list(&player_list, ¤t_size, current_size + 1, new_player2);
Player new_player3 = { 3, "plyerthree" };
update_player_list(&player_list, ¤t_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, ¤t_size, current_size + 1, new_player1);
Player new_player2 = { 2, "plyertwo" };
update_player_list(&player_list, ¤t_size, current_size + 1, new_player2);
Player new_player3 = { 3, "plyerthree" };
update_player_list(&player_list, ¤t_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);
}
}
函数指针的实际用途:
- 事件处理
#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);
}
- 异步编程
- 定时器函数
- 插件架构中的扩展点定义
- 排序算法中的自定义比较函数
- 多线程或并发编程中的线程启动函数
- 用户界面组件中的自定义渲染策略
- 网络请求处理中的协议解析器
- 状态机实现中的状态转换触发器
- 资源管理器中的资源释放策略回调
指针的生命周期
第十五章:杂知识点
多文件编程
- 多文件编程:头文件与编译
- 头文件:
- 组织性
- 重用性
- 编译效率
- 避免重复包含
- 函数声明与实现
- 在一个头文件(.h)中声明
- 在一个源文件(.c)中实现
- 头文件:
- 预处理指令
泛型编程
#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语言的奇淫技巧。

浙公网安备 33010602011771号