前言
- 1 你所能用的正与你手写的效率相同
- 2 你不需要为你没有用到的特性付出
(无脑的调用函数or公式的空壳人类请出门右转)
c
001 scanf and strcpy "_s"bug?
- 微软官方说明
- 1 Visual Studio 库中的许多函数、成员函数、函数模板和全局变量已弃用,改用微软新增的强化函数(在原有基础加_s,但是伴随着参数发生变化需要点开观察)
- debug
//二选一 写在主函数所在文件第一行
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)
#pragma warning(disable : 4996) //关闭之下所有警告
#pragma warning(suppress : 4996)、、关闭下一条警告
scanf("%d",num);
–>
003 内存四区
- c语言实际测试中堆区申请的内存并不能达到理论上的3gb,甚至2gb也会申请失败?
- 1 内存碎片:内存碎片是已经指定的内存快之间随机分散在堆空间内,分配的区块空间的间隙是空闲内存块,当进行3GB(或大块)内存申请,因为内存碎片导致剩余空间不足,或其他部分的代码申请的内存没有释放,导致
- 2 操作系统限制:不同操作系统会对堆内存的最大申请量设置一定的限制。设计者为了防止应用程序占用过多的系统资源。
- 1 初步设计:减少内存碎片的产生,在释放内存时,尽量将内存块回收到内存池中,而不是直接释放。
// 创建内存池
// 参数 size 是内存池的大小
struct mp_pool_s *mp_create_pool(size_t size)
{
struct mp_pool_s *pool;
// 如果 size 小于 PAGE_SIZE 或者 size 不是 PAGE_SIZE 的倍数,则将 size 设置为 PAGE_SIZE
if (size < PAGE_SIZE || size % PAGE_SIZE != 0) {
size = PAGE_SIZE;
}
// 使用 posix_memalign 函数分配对齐的内存
int ret = posix_memalign((void **) &pool,MP_ALIGNMENT, size);
// 如果分配失败,返回 NULL
if (ret)
return NULL;
// 初始化内存池
pool->large = NULL;
pool->current = pool->head = (unsigned char *) pool + sizeof(struct mp_pool_s);
pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
pool->head->end = (unsigned char *) pool + PAGE_SIZE;
pool->head->failed = 0;
// 返回创建的内存池
return pool;
}
// 销毁内存池
// 参数 pool 是要销毁的内存池
void mp_destroy_pool(struct mp_pool_s *pool)
{
struct mp_large_s *large;
// 遍历大块内存,释放已分配的内存
for (large = pool->large; large; large = large->next)
{
if (large->alloc)
free(large->alloc);
}
struct mp_node_s *cur, *next;
// 遍历内存池,释放已分配的内存
cur = pool->head->next;
while (cur)
{
next = cur->next;
free(cur);
cur = next;
}
// 释放内存池
free(pool);
}
// 释放内存
// 参数 pool 是内存池,p 是要释放的内存地址
void mp_free(struct mp_pool_s *pool, void *p)
{
struct mp_large_s *large;
// 遍历大块内存,如果 p 是大块内存的一部分,则释放该内存
for (large = pool->large; large; large = large->next)
{
if (p == large->alloc)
{
free(large->alloc);
large->size = 0;
large->alloc = NULL;
return;
}
}
struct mp_node_s *cur = NULL;
// 遍历内存池,如果 p 是内存池的一部分,则释放该内存
for (cur = pool->head; cur; cur = cur->next)
{
if ((unsigned char *) cur <= (unsigned char *) p && (unsigned char *) p <= (unsigned char *) cur->end)
{
cur->quote--;
if (cur->quote == 0)
{
if (cur == pool->head)
pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
else
cur->last = (unsigned char *) cur + sizeof(struct mp_node_s);
cur->failed = 0;
pool->current = pool->head;
}
return;
}
}
}
- 缺陷和bug1:posix_memalign函数来分配对齐的内存,如果posix_memalign函数调用失败,它会返回一个非零值,并且errno将被设置为一个表示错误的值.但是没有对返回值进行检查,也没有对errno进行检查。这可能会导致在内存分配失败时,程序无法正确处理.
int ret = posix_memalign((void **) &pool,MP_ALIGNMENT, size);//
if (ret)
{
perror("posix_memalign failed");
return NULL;
}
- 缺陷和bug2:内存泄漏:内存释放:在mp_free函数中,我们可以看到在释放内存之前,没有检查pool或p是否为NULL。如果pool或p为NULL,
if (pool == NULL || p == NULL)
{
return;
}
- 缺陷和bug3内存泄漏:在mp_destroy_pool函数中,我们可以看到在释放内存池之前,没有检查pool是否为NULL。如果pool为NULL,
if (pool == NULL)
{
return;
}
- 缺陷和bug2:内存池大小:在mp_create_pool函数中,我们可以看到在分配内存池之前,没有检查size是否为0。如果size为0,
if (size == 0)
{
return NULL;
}
- 2 操作系统和内存管理器的设置,在系统级别进行,而不是在程序中进行的,获取操作系统管理员或者系统级别的开发者权限尝试调整这些限制,或者尝试使用不同的操作系统或内存管理器。
- 检查程序的内存使用情况,尽量减少不必要的内存使用,释放不再需要的内存。
- 如果可能,尝试使用其他的内存分配策略,例如使用连续的内存块,或者使用更高效的内存管理器。
vs2022使用
-
1 代码类型补全提示,工具->文本编辑器->c/c++ lintellisense->启动内联(全选)
-
枚举
/*先switch然后两下tab
会补完到default,光标显示在switch后的变量
这时输入枚举,输完后回车,补完所有枚举的case */
预处理
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用,判断某个宏是否被定义
#if 表达式
程序段1
#else
程序段2
#endif
//表示:如果表达式为真,则编译程序段1,否则编译程序段2.
#include <iostream>
//宏定义注释
int main(void)
{
int a = 0;
#if 0
a = 1;
#endif
printf("%d\n",a);
return 0;
}
#define qwer int//start
/*各种函数*/
#undef//end
#defind 判断宏名是否被定义 //#ifdef和#ifndef仅能一次判断一个宏名,而defined能做到一次判断多个宏名
typedef Data int //可以控制一个类型或者一个数据,更好的调整防止漏掉
- 实例
#include <stdio.h>
#define MAX 10
#define MIN 2
void test()
{
#if defined(MAX) && define (MIN) && //...
printf(“三个宏已全部定义\n”);
#elif MAX==10
printf(“三个宏未全部定义\n”);
#endif
}
糟粕/旧的设计模式(一个操作的淘汰不代表完全不能用)
-
1 类和面向对象弃用
- (1)单例模式(Singleton Pattern):单例模式被广泛使用,但也容易被滥用。它将一个类限制为只能创建一个对象,并提供全局访问点。然而,单例模式经常引入全局状态和紧密耦合的依赖关系,使得代码难以测试和扩展。
- (2)多重继承(Multiple Inheritance):多重继承允许一个类从多个基类派生,但它可能导致继承图的复杂性增加。多重继承可能引入菱形继承问题(Diamond Inheritance Problem),使得代码难以理解和维护。此外,多重继承还可能导致命名冲突和二义性。
- (4)巨大的继承层级(Deep Inheritance Hierarchy):当类的继承层级非常深时,代码的可读性和可维护性可能会下降。巨大的继承层级使得代码的行为和依赖关系变得复杂,同时也增加了代码的耦合性。
- (5)大量的getter和setter方法:在某些情况下,类中存在大量的getter和setter方法,这破坏了封装性,也使代码变得冗长。过多的getter和setter方法可能暴露了过多的类内部细节,增加了代码的耦合性。
- (6)巨大的类(God Object):巨大的类承担了太多的责任和功能,而没有很好地分割成更小的、可管理的部分。这种设计可能导致代码的可读性和可维护性降低,同时也使得代码难以进行单元测试和重用。
-
1 过度使用全局状态(Global State):过度依赖全局状态会导致代码的可读性和可维护性降低。全局状态使得代码的行为变得不可预测,并增加了代码的耦合性。过度依赖全局状态和全局函数会导致代码的可读性和可维护性下降。全局变量和函数使得代码的依赖关系变得复杂,而且难以进行单元测试和重用。
-
2 副作用(Side Effects):副作用指的是对于给定输入,函数或方法除了返回一个结果之外,还会对系统状态或外部资源进行修改。过度依赖副作用可能导致代码的行为不可预测,并增加代码的复杂性和维护难度。
-
3 魔术数字(Magic Numbers):魔术数字是指在代码中出现的没有解释或命名的硬编码常量。魔术数字使得代码难以理解和维护,并且容易引入错误。
-
4 过度使用设计模式:虽然设计模式是一种有用的工具,但过度使用某些设计模式可能导致代码的复杂性增加。在某些情况下,使用设计模式可能会使代码变得冗长、难以理解和维护。
-
5 过度使用注释(Overuse of Comments):注释是一种有用的工具,但过度使用注释可能说明代码本身不够清晰和自解释。过多的注释会导致代码冗长,并且容易出现注释与实际代码不一致的情况。
-
6 过度复杂的条件逻辑(Complex Conditional Logic):过度复杂的条件逻辑使得代码难以理解和维护。复杂的条件语句和嵌套关系容易引入错误,并且使得代码更加脆弱。
数据结构
数组
- 线性搜索
- 二分查找
- 快速排序
- 归并排序
- 堆排序
链表
- 双向链表
- 单向链表
- 循环链表
- 链表反转
- 链表合并
- 判断链表是否有环
- 找到链表中的中间节点
- 删除链表中指定元素
栈
- 栈的实现
- 中缀表达式转后缀表达式
- 后缀表达式求值
- 括号匹配问题
- N皇后问题
队列
- 队列的实现
- 循环队列
- 双端队列
- 优先队列
- 迷宫问题
- 广度优先搜索
树
- 二叉树的遍历(前序、中序、后序)
- 平衡二叉树(AVL树)
- 红黑树
- 堆(最大堆、最小堆)
- 字典树(Trie树)
- Huffman树
- B树和B+树
图
- 邻接表和邻接矩阵的表示
- 深度优先搜索
- 广度优先搜索
- 最短路径问题(Dijkstra算法、Floyd算法)
- 最小生成树问题(Prim算法、Kruskal算法)
哈希表
- 哈希表的实现
- 冲突处理方法(链表法、开放寻址法)
- 一致性哈希
无名结构体和有名结构体
- 有名结构体的用途较为广泛,因为它定义了一种新的数据类型,可以在多个地方重复使用。适合于那些需要在程序中多次使用或者作为函数参数、返回值等场合的数据结构。
- 无名结构体通常用于定义单个复杂数据项或者当做特定作用域内部的一次性使用的数据结构
有名 | 无名 |
---|---|
可重用性高,便于维护和扩展; | 可以直接在定义时初始化,减少代码量。 |
易于理解和交流,提高代码的可读性; | 不便于重复使用,每次使用都需要重新定义; |
可以被用作函数的参数或返回类型,增强了代码的模块化。 | 在复杂程序中,过多使用无名结构体可能会降低代码的可维护性和可读性。 |
在某些仅需一次使用的场景中可能显得稍微繁琐 | 简化了代码,适用于只需要一次性使用的场景; |
开发一个需要处理多个员工信息的系统,使用有名结构体来定义员工信息 | 某个函数内部需要临时组织一些数据,而这组数据在函数外部不再使用 |
- typedef 和无名结构体的组合在游戏中开发
- 1 定义简洁的数据类型
- 1.1 游戏开发涉及大量的数据结构来表示游戏世界中的元素,如角色属性、坐标位置、游戏状态等。使用typedef配合无名结构体
typedef struct { float x, y, z; } Position; typedef struct { int health; int mana; } Stats;
- 2 封装组件数据
- 2.1 游戏通常由多个系统组成,每个系统可能需要处理特定的数据组件
typedef struct { unsigned int id; char name[50]; Position position; Stats stats; } Entity;//Entity封装了一个游戏实体的基本数据,包括位置和状态,使得在处理游戏逻辑时更加方便。
- 3 创建灵活的接口参数
- 3.1 函数或方法需要接受多种类型的数据
typedef struct { int type; union { int intValue; float floatValue; char* stringValue; }data }EventParam;//这个结构体可以用于事件系统,允许发送和接收多种类型的数据,而不必为每种数据类型定义单独的接口。
- 4 优化内存布局
- 4.1
typedef struct { Vector3D position; Vector3D velocity; float mass; unsigned char isActive : 1; // 使用位字段节省空间 } PhysicsComponent;//PhysicsComponent用于存储物理系统中对象的数据
深拷贝/浅拷贝
- 浅拷贝:包含指针的数据结构,浅拷贝仅仅复制指针本身,而不复制指针所指向的数据。这意味着原始数据和复制后的数据会共享同一块内存地址中的数据。
typedef struct
{
int *ptr;
} Example;
Example original, copy;
original.ptr = (int*)malloc(sizeof(int)); // 分配内存
*original.ptr = 10; // 赋值
// 浅拷贝
copy.ptr = original.ptr;//original和copy共享相同的内存地址,因此对copy.ptr或original.ptr的任何修改都会影响到另一个
- 深拷贝不仅复制数据结构的表面层级,还包括复制指针所指向的实际数据。这意味着创建了原始数据的一个完整副本,原始数据和复制后的数据不会共享任何内存地址。
Example deep_copy(Example src)
{
Example dest;
dest.ptr = (int*)malloc(sizeof(int)); // 为dest分配新的内存
if (dest.ptr != NULL)
{
*dest.ptr = *src.ptr; // 复制实际的数据
}
return dest;
}
Example copy = deep_copy(original);
->next
数据类型
-
1 整数类型:使用场景:一般用于存储和操作整数。
- int:用于表示整数值,通常占用机器字长大小。
- short:用于表示较小范围的整数值,通常占用 2 字节。
- long:用于表示较大范围的整数值,通常占用 4 字节或 8 字节。
- long long:用于表示非常大范围的整数值,通常占用 8 字节。
-
固定大小整数类型:不受环境影响的准确字节大小
无符号:size_t uint8_t、int16_t、uint32_t uint64_t uintptr_t
int8_t、int16_t、int32_t、int64_t intptr_t -
2 字符类型:使用场景:处理文本和字符串数据。
- char:用于表示字符。
- signed char:用于表示有符号字符。
- unsigned char:用于表示无符号字符。
-
3 浮点数类型:使用场景:处理实数和十进制数据。
- float:用于表示单精度浮点数,通常占用 4 字节。
- double:用于表示双精度浮点数,通常占用 8 字节。
- long double:用于表示更高精度的浮点数,占用字节大小因平台而异。
-
4 bool:用于表示真(true)或假(false)值。
-
5 指针类型:使用场景:动态内存分配、数组操作、函数参数传递等。
int*、char* 等:用于表示指向不同类型的指针。 -
复合类型:使用场景:组织相关数据和内存优化。
- 枚举类型:enum:用于定义一组命名的常量。使用场景:限制变量取值范围,增加代码可读性。
- struct:用于自定义数据结构。
- union:用于共享内存空间,不同成员使用相同的内存。
-
void:无类型或无返回值。使用场景:函数返回类型、空指针等。
数据结构_算法
- 顺序表和链表 不适用高数量场景
存储方式:
顺序表:使用一段连续的存储空间来存储数据元素,可以通过索引直接访问任何位置的元素。因此,顺序表支持随机访问,时间复杂度为O(1)。
链表:链表则是由一系列的节点组成,每个节点包含数据元素和指向下一个节点的指针。节点在内存中不一定是连续存储的,通过指针将它们串联起来。链表通常只能通过头节点开始顺序访问,因此不支持随机访问,时间复杂度为O(n),其中n为链表的长度。
插入和删除操作:
顺序表:插入和删除可能造成数据移动,特别是在中间或者开头插入/删除元素时,需要移动后续元素,平均时间复杂度为O(n)。
链表:由于链表节点可以通过指针直接链接,插入和删除操作相对简单,并且不需要移动大量数据。在已知位置的情况下,插入和删除操作的时间复杂度为O(1)。
空间复杂度:
顺序表:需要预先分配一定大小的内存空间,可能会浪费部分空间,尤其是当数据量变化不定时。
链表:空间利用率高,可以动态分配内存空间,每个节点的大小可以根据需要而分配。
适用场景:
顺序表:频繁随机访问元素的场景,例如索引表、稀疏矩阵等。
链表: 插入和删除操作频繁,且规模不确定的场景,例如队列、堆栈、大整数的表示等。
- such as : pvz game
顺序表的适用场景
植物列表:
游戏中的植物可以使用顺序表来进行管理。顺序表适合于频繁的随机访问操作,例如通过植物的编号或者位置索引快速访问特定的植物数据,如生命值、攻击力等。
僵尸列表:
类似地,游戏中的僵尸也可以使用顺序表来管理。这样可以快速地遍历和访问不同的僵尸实例,例如在战斗中检查僵尸的位置和状态。
链表的适用场景:
子弹列表:链表的插入和删除操作适合处理不断产生和消失的子弹实例,
动态事件队列:
游戏中的动态事件,例如僵尸的出现或特殊技能的施放,可以使用链表来管理。链表的动态插入和删除特性适合处理随机事件的触发和管理,例如随机生成的奖励物品或者特殊天气效果等。
- 顺序表
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
// #include"SDL_ttf.h"
typedef int DataType;
typedef struct list
{
DataType *data;//数据数组 value表示其中一个的具体数值
int maxCapacity;//capacity容量
int count;//当前位置数 index
} list, *LPSQ;
LPSQ create_list()
{
LPSQ list = (LPSQ)calloc(1, sizeof(*list));
//sizeof(list)list是指针类型,而不是结构体本身
//应该写为sizeof(struct list)或sizeof(*list)
assert(list);
return list;
}
void destroy_list(LPSQ list) // destory 销毁
{
free(list->data);
list->data = NULL;
list->maxCapacity = 0;
list->count = 0;
free(list);
}
void checkFullAndExpand(LPSQ list) // check检查 enpand 扩大
{
if (list->count >= list->maxCapacity)
{
int newCapacity = list->maxCapacity * 2;
int *newData = (int *)realloc(list->data, newCapacity * sizeof(int));
if (newData == NULL)
{
fprintf(stderr, "Error: Memory reallocation failed.\n");
exit(EXIT_FAILURE);
}
list->data = newData;
list->maxCapacity = newCapacity;
}
}
bool get_value_sqlist(LPSQ list, int index, int *value) // value数值
{
if (index < 0 || index >= list->count)
{
fprintf(stderr, "Error: Get index out of bounds.\n");
return false;
}
*value = list->data[index];
return true;
}
bool insert_list(LPSQ list, int index, int value)
{
if (index < 0 || index >= list->count)
{
fprintf(stderr, "ERROOR:Insert index out of bounds.\n");
return false;
}
checkFullAndExpand(list);
// 将index及其之后的元素后移一位
for (int i = list->count; i > index; --i)
{
list->data[i] = list->data[i - 1];
}
list->data[index] = value;
list->count++;
return true;
}
void insert_list_end(LPSQ list, DataType data)
{
assert(list);
if (list->count == list->maxCapacity)
{
list->maxCapacity += 10;
DataType *temp = (DataType *)realloc(list->data, sizeof(DataType) * list->maxCapacity);
assert(temp);
list->data = temp;
}
list->data[list->count++] = data;
}
bool delete_sqlist(LPSQ list, int index)
{
if (index < 0 || index >= list->count)
{
fprintf(stderr, "Error: Delete index out of bounds.\n");
return false;
}
for (int i = index; i < list->count-1; i++)
{
list->data[i]=list->data[i+1];
}
list->count--;
return true;
}
void print_sqlpst(LPSQ list)
{
for (int i = 0; i < list->count; i++)
{
printf("%d ", list->data[i]);
}
printf("\n");
}
- 单向链表
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义链表节点结构体
typedef struct ListNode
{
int data;
struct ListNode *next;
} ListNode, *LPLN;
// 定义链表结构体
typedef struct
{
LPLN head; // 头节点指针
LPLN tail; //尾节点指针
int size; // 链表当前大小
} LinkedList, *LPLINK;
LPLINK create_linkedlist()
{
LPLINK list = (LPLINK)malloc(sizeof(LinkedList));
if (list)
{
list->head = NULL;
list->size = 0;
}
return 0;
}
// 销毁链表
void destroy_linkedlist(LinkedList *list)
{
if (list)
{
LPLN current = list->head;
while (current)
{
LPLN next = current->next;
free(current);
current = next;
}
free(list);
}
}
// 在链表末尾插入节点
void insert_end(LPLINK list, int value)
{
LPLN newNode = (ListNode *)malloc(sizeof(ListNode));
if (newNode)
{
newNode->data = value;
newNode->next = NULL;
if (list->head == NULL)
list->head = newNode;
else
{
LPLN current = list->head;
while (current)
{
current = current->next;
}
current->next = newNode;
}
list->size++;
}
else fprintf(stderr, "Error: Memory allocation failed.\n");
}
//在链表末尾插入节点优化思想
- 优化单向链表
-
创建链表函数 create_linkedlist:
- 哨兵节点作为头节点:list->sentinel = (LPLN)malloc(sizeof(ListNode));
- 尾指针初始化:list->tail = list->sentinel;
- 大小初始化:list->size = 0;
-
销毁链表函数 destroy_linkedlist:
- 释放节点内存:free(current);
- 释放哨兵节点内存:free(list->sentinel);
- 释放链表结构内存:free(list);
-
在链表尾部插入元素函数 insert_end:
- 链接新节点到尾部:list->tail->next = newNode;
- 更新尾指针为新节点:list->tail = newNode;
- 增加链表大小:list->size++;
-
#include <stdio.h>
#include <stdlib.h>
typedef struct ListNode {
int data;
struct ListNode *next;
} ListNode, *LPLN;
typedef struct {
ListNode *sentinel; // 哨兵节点作为头节点
ListNode *tail; // 尾节点指针
int size; // 链表大小
} LinkedList, *LPLINK;
// 创建链表
LPLINK create_linkedlist() {
LPLINK list = (LPLINK)malloc(sizeof(LinkedList));
if (list) {
list->sentinel = (LPLN)malloc(sizeof