限制作用域:
static声明中使用全局变量、函数 ,仅当前文件内可用,其他文件不能引用
static修饰的局部变量只能在本函数中使用。
延长生命周期:
static修饰的变量生命周期为整个程序
存放位置:
static修饰的变量存放在静态区
初始化:
static变量未赋初值时初值为0,且只初始化一次
const 是常量化的意思;
可以修饰变量,可以修饰指针。
当修饰变量的时候,因为不可以通过变量名对变量的值进行修改所以在定义变量的时候需要给变量初始化;
当修饰指针的时候,const位置不同,修饰的指针的指向或内容不能改变。
char *const a 指向不可修改,内容可以修改
char const *a 内容不可以修改,指向可以修改
const char *a 内容不可以修改,指向可以修改
1.处理时机:
typedef:typedef 创建类型别名,编译器在编译时会对其进行处理。
#define:创建宏定义,预处理器在预处理阶段会对其进行处理。即在编译之前,预处理器会将代码中的宏定义替换为相应的内容。
3.作用范围:
typedef:typedef 定义的类型别名在作用域内有效,并且可以在多个函数或文件中使用。
#define:#define 定义的宏定义在定义位置后全局有效,可以在整个代码中使用,直到遇到 #undef 指令或代码结束。
4.语法格式:
typedef:typedef 的语法格式为:typedef
#define:#define 的语法
5.类型安全:
typedef:typedef 创建的别名是类型安全的,因为它们实际上是原类型的别名。
#define:#define 创建的宏定义不进行类型检查,它仅仅是简单的文本替换。
6.适用场景:
typedef:typedef 通常用于创建复杂类型的别名,使代码更易读和可维护。
#define:#define 主要用于创建简单的常量或函数宏,用于简化代码或定义一些特定的标识符。
枚举是一种用户自定义的数据类型,它为一组相关的整数常量赋予了有意义的名称,枚举里成员系统会自动赋初值,第一个成员为0,依次类推;如果程序员想赋值的,假设第一个成员赋值为1,那么第二个成员系统会赋值为2,依次类推
volatile 主要有以下作用:
volatile是一个关键字,用于在 C 和 C++ 中修饰变量,它主要用于告诉编译器该变量的值可能在程序执行期间发生变化,从而禁止编译器对该变量进行优化。
外部引用,引用其他文件中的全局变量或函数。
strlen是函数,用于计算字符串的长度;不包含`\0`;
sizeof是关键字,用于计算变量、数组、其他数据类型所占内存空间的大小;当sizeof计算字符串长度的时候,包含`\0`;
数组
数组是一个相同类型的数据集合
数组元素可以使用数组索引随机访问
数组的数据元素在内存中连续存储
插入和删除非常耗时,时间为O(n)
数组的内存是静态分配的,在编译期间完成
数组的大小必须在数组的声明或初始化的时候指定
链表
链表是一个有相同数据类型的有序集合,其中每个元素使用指针链接
链表不允许随机访问,链表创建一个指针指向相应的数据
链表的插入和删除非常快,时间为O(1)
链表的内存分配-是动态的,在运行时动态分配
链表的大小随元素的插入或删除动态变化
链表和内核链表的区别:
指针就是地址,指针变量就是存放地址的变量;指针可以使用简单的运算符操作;指针加一或者自加,代表指向下一个元素;对于32位系统,指针占4字节
数组是同名类型是数组数据的集合,内存连续。数组的首地址,是地址常量,不可以进行自加等操作;
指针数组的本质是数组,数组里存放的是指针。int *p[3]
数组指针的本质是指针,指向数组的指针称为数组指 针。int (*p)[3]可以间接访问二维数组。
函数指针本质是指针,指向函数的指针 ,一般用做函数的参数,实现代码复用,也可以作为结构体成员,指向某个函数。 int (*p)(int,int)
二者都是构造数据类型
1)结构体:让C语言实现面向对象的思想。结构体使用的时候,结构体中每一个成员都有自己的内存空间,计算结构体大小的时候要注意内部字节对齐;
2)共用体又叫联用体,大小等于成员中占内存最大的那个大小。合体,每一个成员都共享内存空间。因此共用体大小等于成员中占内存最大的那个大小。
结构体的大小并不是简单的将每个成员的大小相加就能得到的
sizeof(struct 结构体名); // 结构体类型的大小
对齐数:该结构体成员变量自身的大小与编译器默认(64位是8、32位是4)的一个大小比较,取小值
#pragma 是一个预处理指令,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作,#pragma pack 的 主要作用就是改变编译器的内存对齐方式
(1)结构体各个成员变量的首地址必须是 min{自身对齐值,指定对齐值的整数倍。
(2)结构体各个成员相对于结构体起始地址的偏移量(offset)是 min{该成员数据类型大小,指定对齐值} 的整数倍,如有需要,编译器会在成员之间加上填充字节。
(3)结构体分配的总空间大小必须是 min {其最宽基本数据类型成员,指定对齐值} 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
(1)管理方式不同。
栈编译器自动管理,无需程序员手工控制;
堆空间的申请释放工作 由程序员控制,通过 malloc/free申请释放空间,容易产生内存泄漏。
(2)空间大小不同。
栈是向低地址扩展的数据结构,是一块连续的内存区域。
堆是向高地址扩展的数据结构,是不连续的内存区域。
(3)是否产生碎片。
对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。
(4)增长方向不同。
堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向。
栈区存放局部变量,函数形参,函数返回值。
代码段(Text Segment):存放程序的机器指令。
数据段(Data Segment):存放已初始化的全局变量和静态变量。
BSS段(BSS Segment):存放未初始化的全局变量和静态变量。
堆(Heap):动态分配的内存区域,如使用malloc或new分配的内存。
栈(Stack):用于局部变量、形参、返回值存储。
定义:全局变量是定义在函数外部的变量,局部变量是定义在函数内部的变量
存储位置:全局变量存储在全局区,局部变量存储在栈区
作用域:全局变量可以在程序任意位置使用,局部变量只能在函数内部使用
生命周期:全局变量的生命周期为整个程序,程序结束空间释放,局部变量生命周期为本函数,函数结束空间释放
初始化:全局变量未初始化初值为0,局部变量未初始化时值为随机值
当程序尝试访问它没有权限访问的内存地址时
段错误通常是由以下几种情况引起的:
要解决段错误问题,可以通过以下几种方式:
内存泄漏(Memory Leak): 内存泄漏是指程序在动态分配内存后,未释放不再使用的内存,导致这部分内存永远无法被回收,从而造成内存的浪费。如果程序中存在内存泄漏,随着程序的执行,内存占用会逐渐增加,最终可能导致程序崩溃或系统资源耗尽。内存泄漏的常见原因包括:
野指针(Wild Pointer): 野指针是指指向未知或者无效内存地址的指针。野指针通常产生于以下几种情况:
#define MIN(a, b) ((a) < (b)?(a) : (b))
void reverseString(char *str) {
int length = strlen(str);
char *start = str;
char *end = str + length - 1;
while (start < end) {
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
int main() {
char str[] = "Hello, World!";
printf("原始字符串:%s\n", str);
reverseString(str);
printf("逆序输出:%s\n", str);
return 0;
}
void bubbleSort(int arr[], int n) {
int i, j;
for (i = 0; i < n-1; i++) {
// 每次循环将最大的元素冒泡到末尾,因此每轮循环只需比较前 n-i-1 个元素
for (j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
// 如果前一个元素大于后一个元素,则交换它们的位置
int temp = arr[j]; //arr[j] ^= arr[j + 1];
arr[j] = arr[j+1]; //arr[j + 1] ^= arr[j];
arr[j+1] = temp; //arr[j] ^= arr[j + 1];
}
}
}
}
size_t my_strlen(const char *str) {
size_t len = 0;
while (*str) {
len++;
str++;
}
return len;
}
char *my_strcpy(char *dest, const char *src) {
char *temp = dest; // 暂存dest字符串的首地址
while (*src) {
*dest = *src;
dest++;
src++;
}
*dest = '\0'; // 在目标字符串末尾加上 null 终止符
return temp;
}
char *my_strcat(char *dest, const char *src) {
char *temp = dest;
// 移动 dest 指针到目标字符串的末尾
while (*dest) {
dest++;
}
// 将源字符串复制到目标字符串的末尾
while (*src) {
*dest = *src;
dest++;
src++;
}
// 添加目标字符串的 null 终止符
*dest = '\0';
return temp;
}
#include
int my_strcmp(const char *str1, const char *str2) {
while (*str1 && *str2) {
if (*str1 != *str2) {
return *str1 - *str2;
}
str1++;
str2++;
}
// 如果两个字符串长度不等,返回长度差值
return *str1 - *str2;
}
int main() {
const char *str1 = "apple";
const char *str2 = "banana";
int result = my_strcmp(str1, str2);
if (result < 0) {
printf("%s 小于 %s\n", str1, str2);
} else if (result == 0) {
printf("%s 等于 %s\n", str1, str2);
} else {
printf("%s 大于 %s\n", str1, str2);
}
return 0;
}
#include
int main(int argc, char const *argv[])
{
int a[10][10] = {0}, i, j;
for (i = 0; i < 10; i++)
{
a[i][0] = 1;
for (j = 1; j <= i; j++)
a[i][j] = a[i - 1][j] + a[i - 1][j - 1];
}
for (i = 0; i < 10; i++)
{
for (j = 0; j <= i; j++)
printf("%-5d", a[i][j]);
putchar('\n');
}
return 0;
}运行上述代码,将会输出杨辉三角的前10行:
Copy code
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
每一行的数字都代表一个组合数,它是由 C(n, k) 组成,其中 n 为行数减1,k 为列数减1。在这个例子中,C(n, k) = triangle[n][k]。
int my_atoi(const char *str) {
int result = 0;
int sign = 1;
// 跳过字符串前面的空格字符
while (*str == ' ') {
str++;
}
// 判断符号位
if (*str == '-' || *str == '+') {
sign = (*str == '-') ? -1 : 1;
str++;
}
// 将数字字符转换为整数,并累加到 result
while (*str >= '0' && *str <= '9') {
result = result * 10 + (*str - '0');
str++;
}
return result * sign;
}
int count(int x) { int num=0; for (int i = 0; i < 8; i++)//一个字节是8位 { if((x&(1< num++; } return num; } |
目标、依赖、命令
# 编译器和编译选项
CC := gcc
CFLAGS := -Wall -Werror -g -c
# 目标文件和可执行文件名
OBJ_FILES := main.o utils.o
TARGET := my_program
# 默认规则:生成可执行文件
$(TARGET): $(OBJ_FILES)
$(CC) $(CFLAGS) $^ -o $@
# 生成目标文件 main.o
main.o: main.c utils.h
$(CC) $(CFLAGS) -c $< -o $@
# 生成目标文件 utils.o
utils.o: utils.c utils.h
$(CC) $(CFLAGS) -c $< -o $@
# 伪目标:清理临时文件 特殊规则(还可以安装文件)
.PHONY: clean
clean:
rm -f $(TARGET) $(OBJ_FILES)
# 注释以 # 开头,$@ 表示目标文件,$< 表示第一个依赖文件,$^ 表示所有依赖文件等。
文件和目录操作:
ls: 列出文件和目录
cd: 切换目录
pwd: 显示当前工作目录
touch: 创建空文件或更新文件的访问时间
mkdir: 创建新目录
cp: 复制文件或目录
mv: 移动文件或目录
rm: 删除文件或目录
cat: 查看文件内容
more 或 less: 分页查看文件内容
head: 查看文件开头部分
tail: 查看文件结尾部分
文件内容处理:
grep: 在文件中搜索指定字符串
find: 在目录中查找文件
wc: 统计文件中的行数、字数和字节数
sort: 对文件内容进行排序
uniq: 删除文件中的重复行
cut: 从文件中截取字段
sed: 流编辑器,用于处理文本流
文件权限和所有权管理:
chmod: 修改文件或目录的权限
chown: 修改文件或目录的所有者和所属
系统信息查看:
date: 显示或设置系统时间和日期
uptime: 查看系统运行时间和负载
ps: 显示进程状态
top: 实时显示系统进程状态
df: 查看磁盘空间使用情况
du: 查看文件和目录的磁盘使用情况
网络命令:
ping: 测试网络连通性
ifconfig 或 ip: 显示或配置网络接口信息
netstat: 显示网络状态信息
ssh: 安全远程登录
scp: 安全复制文件或目录
wget 或 curl: 下载文件或网页
系统管理:
reboot: 重启系统
shutdown: 关闭系统
useradd: 添加新用户
passwd: 修改用户密码
userdel: 删除用户
su: 切换用户
压缩和解压缩:
tar: 打包和解包文件 -xvf
gzip 或 gunzip: 压缩和解压缩文件
zip 或 unzip: 压缩和解压缩zip文件
#ifndef是C语言预处理指令中的条件有编译指令之一,其作用是防止头文件被多次包含,从而避免重复定义错误。
#ifndef HEADER_FILE_NAME_H
#define HEADER_FILE_NAME_H
// 头文件内容
#endif
启动GDB:在终端中运行gdb命令,然后在GDB提示符中输入要调试的可执行文件的路径,例如:gdb ./my_program。
设置断点: 使用break命令设置断点,例如:break main在main函数处设置断点。
运行程序:在GDB中使用run或r命令运行程序,例如:run。
单步执行:使用step或s命令单步执行程序,按行执行并进入函数。
运行到断点:使用continue或c命令运行程序,直到遇到下一个断点。
查看变量:使用print或p命令查看变量的值,例如:print my_variable。
查看堆栈:使用backtrace或bt命令查看函数调用堆栈。
跟踪变量:使用watch命令跟踪 变量的值变化,例如:watch my_variable。
删除断点:使用delete命令删除断点,例如:delete 1删除序号为1的断点。
退出GDB:使用quit或q命令退出GDB。
预处理:在编译之前,需要对源代码进行预处理。
gcc -E hello.c -o hello.i
编译:接下来,对预处理后的文件进行编译 。
gcc -S hello.i -o hello.s
汇编:将汇编代码转换成机器代码。
gcc -C hello.s -o hello.o
链接:最后,将目标文件和其他可能需要的库文件链接在一起,生成可执行文件。
gcc hello.o -o hello
link_node_t *CreateEpLinkList()
{
link_node_t *h = (link_node_t *)malloc(sizeof(link_node_t));
/*开辟空间,返回值为结构体指针,开辟的空间强转为结构体指针,空间大小为结构体大小*/
if (NULL == h)
{
printf("开辟空间失败\n");
return NULL;
}
h->next = NULL;
return h;
}
//2.向单向链表的指定位置插入数据,p保存链表的头指针 post 插入的位置 data插入的数据
int InsertIntoPostLinkList(link_node_t *p, int post, datatype data)
{
/*创建新节点 */n
link_node_t *pnew = CreateEpLinkList();
pnew->next = NULL; /*链接节点*/
pnew->data = data; /*写入数据*/
/*进行插入 */
if (post < 0 || p == NULL)
{
printf("插入失败\n");
return -1;
}
for (int i = 0; i < post; i++)
{
p = p->next;
}
pnew->next = p->next; /*链接节点 尾插第一个p->next为空.
头插和中间插p->next为下一个数据*/
p->next = pnew;
return 0;
}
//5.删除单向链表中指定位置的数据 post 代表的是删除的位置
int DeletePostLinkList(link_node_t *p, int post)
{
if (post < 0 || post >= LengthLinkList(p) || IsEpLinkList(p))
{
printf("删除错误\n");
}
for (int i = 0; i < post; i++)
{
p = p->next;
}
link_node_t *p_del = p->next;
p->next = p_del->next;
free(p_del);
p_del = NULL
return 0;
}
//10.删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
int DeleteDataLinkList(link_node_t *p, datatype data)
{
if (IsEpLinkList(p)) /*判断链表是否为空*/
return -1;
link_node_t *p_del = NULL;
while (p->next != NULL) /*循环终止条件为p->指向空*/
{
if (p->next->data == data) 看i修辞学
{
p_del = p->next;
p->next = p_del->next;
free(p_del);
p_del = NULL;
}
}
return 0;
}
//7.修改指定位置的数据 post被修改的位置 data修改成的数据
int ChangePostLinkList(link_node_t *p, int post, datatype data)
{
if (post < 0 || post >= LengthLinkList(p) || IsEpLinkList(p))
{
printf("修改失败\n");
return -1;
}
for (int i = 0; i <= post; i++)
{
p = p->next;
}
p->data = data; /*链接节点 */
return 0;
}
//8.查找指定数据出现的位置 data被查找的数据 //search 查找
int SearchDataLinkList(link_node_t *p, datatype data)
{
if (IsEpLinkList(p)) /*判断链表是否为空*/
return -1;
int i = 0;
while (p->next != NULL) /*循环终止条件为p->指向空*/
{
p = p->next;
if (p->data == data)
{
return i;
}
i++;
}
}
//9.转置链表
void ReverseLinkList(link_node_t *p)
{
link_node_t *q = p->next;
link_node_t *temp = NULL;
/*将头节点和下一个节点断开 */
p->next = NULL;
/*遍历无头单向节点 */
while (q != NULL)
{
temp = q->next;
/*进行插入操作 */
q->next = p->next;
p->next = q;
q = temp;
}
}
单链表(Singly Linked List)和双向链表(Doubly Linked List)是两种常见的链式存储结构,它们在数据结构中有一些不同之处。
单链
双向链表:
区别总结:
原理:冒泡排序是一种简单的排序算法,每次比较相邻的两个元素,如果它们的顺序错误,则交换位置,通过多次遍历,将最大的元素逐渐冒泡到正确的位置上。
时间复杂度:冒泡排序的平均时间复杂度为O(n²),其中n是待排序元素的数量,最好情况下时间复杂度为O(n)
二叉树:是一种特殊的树结构,每个节点最多有两个子树,分别称为左子节点和右子节点
满二叉树:除了叶子节点外的每一个节点都有两个子节点。
(1)前序遍历:先访问根节点,然后按照左子树、右子树的顺序进行遍历
(2)中序遍历:按照左子树、根节点、右子树的顺序进行遍历
(3)后序遍历:按照左子树、右子树、根节点的顺序进行遍历
顺序查找:从头到尾逐个比较查找,时间复杂度为O(n)
二分查找:对有序数组进行查找,每次将查找区间二分,并与目标值进行比较,时间复杂度为O(nlog n )
哈希表:是一种通过哈希函数将键映射到存储位置的数据结构。查找元素时,先使用哈希函数计算出键的哈希值,然后根据哈希值找到对应的存储位置,在哈希冲突的情况下,通常采用开放地址法、活链地址法来解决.
冒泡排序:比较相邻元素,将较大的元素向后移动,时间复杂度为O(n²)
选择排序:从数列中找最小值,找到后和第一个位置的数据进行交互,再从剩下数中找最小值,依次类推。
快速排序:采用“分治”的思想,对于一组数据,选择一个基准元素(base),通常选择第一个或最后一个元素,通过第一轮扫描,比base小的元素都在base左边,比base大的元素都在base右边,再有同样方法递归排序这两部分,直到序列中所有数据均有序为止。