知之愈明,行之愈笃
a.认识结构体
结构体是一些值的集合,这些值称为成员变量.结构体的每个成员可以是不同类型的变量.
//声明一个结构体类型
//声明一个学生类型(Stu就是你对这个结构体的称呼)
struct Stu
{
char name[20];
char tele[12]; //成员变量
char sex[10];
int age;
}s4,s5,s6;//全局变量
struct Stu s3;//全局变量
int main()
{
//创建结构体变量(局部的)
struct Stu s1;
struct Stu s2;
}
b.结构体自引用
//结构体的自引用
typedef struct Node
{
int data;
//struct Node n; sizeof(struct Node)无限包含下去错误
struct Node* next;//找到同类型的其它变量
}Node;//你只用关心这里就好了,我们给结构体创建了另一个名字
c.结构体嵌套使用
struct T
{
double weight;
int age;
};
struct S
{
char c;
int a;
double d;
char arr[20];
struct T st;//嵌套了上一个结构体
};
int main()
{
struct S s = { 'c',100,3.14,"hello disposable",{55.6,20} };
printf("%c %d %lf %s %lf %d\n", s.c, s.a, s.d, s.arr,s.st.weight,s.st.age);
}
C语言的内存模型是基于物理内存的抽象模型,它将物理内存划分为不同的区域,每个区域有不同的作用和特点。C语言的内存模型主要包括以下几个部分:
代码区:存放程序的指令代码,通常是只读的。
数据区:存放程序中已经初始化的全局变量和静态变量。
BSS段:存放未初始化的全局变量和静态变量,通常被初始化为0。
堆区:动态分配内存的区域,由程序员手动管理。
栈区:存放函数的局部变量和函数调用的上下文信息,由编译器自动管理。
在C语言中,变量的存储位置和生命周期由其作用域和存储类别决定。作用域指的是变量在程序中的可见范围,存储类别指的是变量在内存中的存储方式和生命周期。C语言中的存储类别包括自动存储类别、静态存储类别、寄存器存储类别和外部存储类别。
自动存储类别的变量通常存储在栈区,它们的生命周期与函数调用的生命周期相同。静态存储类别的变量通常存储在数据区或BSS段,它们的生命周期与程序的运行周期相同。寄存器存储类别的变量通常存储在CPU的寄存器中,它们的生命周期与函数调用的生命周期相同。外部存储类别的变量通常存储在数据区或BSS段,它们的生命周期与程序的运行周期相同,但它们可以被其他文件访问。
当我们定义一个变量时,它的存储位置和生命周期由其作用域和存储类别决定。下面是一些不同类型变量存储在不同区域的例子:
全局变量:全局变量通常存储在数据区或BSS段中,它们的生命周期与程序的运行周期相同。例如:
int global_var = 10; // 存储在数据区
static int static_global_var = 20; // 存储在BSS段
局部变量:局部变量通常存储在栈区中,它们的生命周期与函数调用的生命周期相同。例如:
void func() {
int local_var = 30; // 存储在栈区
static int static_local_var = 40; // 存储在数据区
}
动态分配内存:动态分配内存通常存储在堆区中,由程序员手动管理。例如:
int* ptr = (int*)malloc(sizeof(int)); // 存储在堆区
字符串常量:字符串常量通常存储在代码区中,它们是只读的。例如:
char* str = "Hello, world!"; // 存储在代码区
需要注意的是,这些变量的存储位置和生命周期可能会因为编译器、操作系统和硬件平台的不同而有所差异。
C语言中基本变量的类型及字节长度如下:
类型 字节长度
char 1
short 2
int 4
long 4或8
float 4
double 8
下面给出数组、字符数组、链表、队列、栈、图的数据结构用C语言定义的示例:
数组
数组是一种线性数据结构,它由一组相同类型的元素组成,这些元素在内存中是连续存储的。数组的定义方式如下:
// 定义一个包含5个int类型元素的数组
int arr[5];
字符数组
字符数组是一种特殊的数组,它的元素类型为char,用于存储字符串。字符数组的定义方式如下:
// 定义一个包含10个字符的字符数组
char str[10];
链表
链表是一种非线性数据结构,它由一组节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表的定义方式如下:
// 定义一个包含int类型数据的链表节点
struct ListNode {
int val;
struct ListNode *next;
};
队列
队列是一种先进先出(FIFO)的线性数据结构,它由一组元素组成,支持在队尾插入元素,在队头删除元素。队列的定义方式如下:
// 定义一个包含int类型数据的队列
struct Queue {
int *data;
int front;
int rear;
int size;
};
栈
栈是一种后进先出(LIFO)的线性数据结构,它由一组元素组成,支持在栈顶插入元素,在栈顶删除元素。栈的定义方式如下:
// 定义一个包含int类型数据的栈
struct Stack {
int *data;
int top;
int size;
};
图
图是一种非线性数据结构,它由一组节点和一组边组成,每个节点包含一个数据元素和一组指向其他节点的边。图的定义方式如下:
// 定义一个包含int类型数据的图节点
struct GraphNode {
int val;
struct GraphNode **neighbors;
int neighborsSize;
};
C语言中,静态局部变量和普通局部变量的区别主要有以下几点:
存储位置不同
静态局部变量存储在静态数据区,而普通局部变量存储在栈中。静态数据区是程序在编译时就分配好的一块内存,它在程序运行期间一直存在,直到程序结束才被释放。而栈是程序在运行时动态分配的一块内存,它的生命周期与函数调用的生命周期相同。
生命周期不同
静态局部变量的生命周期与程序的生命周期相同,即在程序运行期间一直存在,直到程序结束才被释放。而普通局部变量的生命周期与函数调用的生命周期相同,即在函数调用结束时被释放。
初值不同
静态局部变量在第一次使用之前会被自动初始化为0或者NULL,而普通局部变量不会被自动初始化,它们的初值是未定义的。
可见性不同
静态局部变量的作用域仅限于定义它的函数内部,但是它的值在函数调用结束后仍然保持不变,下次调用该函数时仍然可以使用。而普通局部变量的作用域也仅限于定义它的函数内部,但是它的值在函数调用结束后就被销毁了,下次调用该函数时需要重新初始化。
需要注意的是,静态局部变量和普通局部变量的使用场景不同。静态局部变量适用于需要在函数调用之间保持状态的情况,而普通局部变量适用于临时存储数据的情况。在使用静态局部变量时,需要注意它的生命周期和可见性,避免出现意外的错误。
C语言中关于字符串赋值、比较、反转、初始化、拼接、截取的函数如下:
字符串赋值可以使用strcpy函数,它的原型如下:
char *strcpy(char *dest, const char *src);
其中,dest是目标字符串的指针,src是源字符串的指针。该函数将源字符串复制到目标字符串中,并返回目标字符串的指针。
字符串比较可以使用strcmp函数,它的原型如下:
int strcmp(const char *s1, const char *s2);
其中,s1和s2是要比较的两个字符串的指针。该函数比较两个字符串的大小,如果s1小于s2,则返回一个负数;如果s1等于s2,则返回0;如果s1大于s2,则返回一个正数。
字符串反转可以使用strrev函数,它的原型如下:
char *strrev(char *str);
其中,str是要反转的字符串的指针。该函数将字符串中的字符顺序反转,并返回反转后的字符串的指针。
字符串初始化可以使用memset函数,它的原型如下:
void *memset(void *s, int c, size_t n);
其中,s是要初始化的字符串的指针,c是要初始化的字符,n是要初始化的字符数。该函数将字符串中的每个字符都设置为指定的字符。
字符串拼接可以使用strcat函数,它的原型如下:
char *strcat(char *dest, const char *src);
其中,dest是目标字符串的指针,src是要拼接的字符串的指针。该函数将源字符串拼接到目标字符串的末尾,并返回目标字符串的指针。
字符串截取可以使用strncpy函数,它的原型如下:
char *strncpy(char *dest, const char *src, size_t n);
其中,dest是目标字符串的指针,src是源字符串的指针,n是要截取的字符数。该函数将源字符串中的前n个字符复制到目标字符串中,并返回目标字符串的指针。
需要注意的是,上述函数的使用需要注意字符串的长度和内存分配,避免出现缓冲区溢出等问题。在使用字符串函数时,需要仔细阅读函数的文档,了解函数的使用方法和注意事项。
strlen函数用于计算字符串的长度,它的原型如下:
size_t strlen(const char *s);
其中,s是要计算长度的字符串的指针。该函数返回字符串的长度,不包括字符串末尾的空字符。
示例:
#include
#include
int main() {
char str[] = "hello world";
size_t len = strlen(str);
printf("字符串的长度为:%zu\n", len);
return 0;
}
//输出结果:
//字符串的长度为:11
strchr函数用于在字符串中查找指定字符的位置,它的原型如下:
char *strchr(const char *s, int c);
其中,s是要查找的字符串的指针,c是要查找的字符。该函数返回指向第一个匹配字符的指针,如果未找到匹配字符,则返回NULL。
示例:
#include
#include
int main() {
char str[] = "hello world";
char *p = strchr(str, 'o');
if (p != NULL) {
printf("找到了字符'o',位置为:%ld\n", p - str);
} else {
printf("未找到字符'o'\n");
}
return 0;
}
输出结果:
找到了字符'o',位置为:4
strstr函数用于在字符串中查找指定子串的位置,它的原型如下:
char *strstr(const char *haystack, const char *needle);
其中,haystack是要查找的字符串的指针,needle是要查找的子串的指针。该函数返回指向第一个匹配子串的指针,如果未找到匹配子串,则返回NULL。
示例:
#include
#include
int main() {
char str[] = "hello world";
char *p = strstr(str, "world");
if (p != NULL) {
printf("找到了子串'world',位置为:%ld\n", p - str);
} else {
printf("未找到子串'world'\n");
}
return 0;
}
输出结果:
找到了子串'world',位置为:6
sprintf函数用于将格式化的数据写入字符串中,它的原型如下:
int sprintf(char *str, const char *format, ...);
其中,str是要写入的字符串的指针,format是格式化字符串,…是可变参数列表。该函数将格式化的数据写入字符串中,并返回写入的字符数。
示例:
#include
#include
int main() {
char str[100];
int num = 123;
float f = 3.14;
sprintf(str, "num=%d, f=%.2f", num, f);
printf("%s\n", str);
return 0;
}
输出结果:
num=123, f=3.14
需要注意的是,在使用sprintf函数时,需要确保目标字符串的长度足够大,以避免缓冲区溢出等问题。
以上字符串处理函数的头文件为
C语言中,动态申请内存、释放内存、保证内存安全的函数如下:
动态申请内存
动态申请内存可以使用malloc函数,它的原型如下:
void *malloc(size_t size);
其中,size是要申请的内存大小,单位是字节。该函数返回一个指向申请到的内存块的指针,如果申请失败,则返回NULL。
示例:
#include
#include
int main() {
int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
*p = 123;
printf("申请到的内存地址为:%p,值为:%d\n", p, *p);
free(p);
} else {
printf("申请内存失败\n");
}
return 0;
}
输出结果:
申请到的内存地址为:0x7f8c7ac02a40,值为:123
释放内存
释放内存可以使用free函数,它的原型如下:
void free(void *ptr);
其中,ptr是要释放的内存块的指针。该函数将指定的内存块释放回系统,以便其他程序可以使用。
示例:
#include
#include
int main() {
int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
*p = 123;
printf("申请到的内存地址为:%p,值为:%d\n", p, *p);
free(p);
printf("内存已释放\n");
} else {
printf("申请内存失败\n");
}
return 0;
}
输出结果:
申请到的内存地址为:0x7f8c7ac02a40,值为:123
内存已释放
需要注意的是,在使用free函数释放内存时,需要确保指定的内存块是通过malloc函数申请的,否则可能会导致程序崩溃或者内存泄漏等问题。
保证内存安全
为了保证内存安全,C语言提供了一些函数,可以检查内存访问是否越界,避免出现缓冲区溢出等问题。常用的内存安全函数如下:
memset函数:用于将一段内存块清零,可以避免出现敏感信息泄露的问题。
memcpy函数:用于将一段内存块复制到另一段内存块中,可以避免出现内存访问越界的问题。
memmove函数:与memcpy函数类似,但是可以处理内存块重叠的情况。
calloc函数:与malloc函数类似,但是会将申请到的内存块清零,可以避免出现敏感信息泄露的问题。
当然,下面给出一些常用的C语言保证内存安全函数的用法示例:
memset函数
memset函数用于将一段内存块清零,它的原型如下:
void *memset(void *s, int c, size_t n);
其中,s是要清零的内存块的指针,c是要设置的值,n是要清零的字节数。该函数将指定的内存块中的每个字节都设置为指定的值。
示例:
#include
#include
int main() {
char str[100] = "hello world";
printf("清零前的字符串为:%s\n", str);
memset(str, 0, sizeof(str));
printf("清零后的字符串为:%s\n", str);
return 0;
}
输出结果:
清零前的字符串为:hello world
清零后的字符串为:
memcpy函数
memcpy函数用于将一段内存块复制到另一段内存块中,它的原型如下:
void *memcpy(void *dest, const void *src, size_t n);
其中,dest是目标内存块的指针,src是源内存块的指针,n是要复制的字节数。该函数将源内存块中的内容复制到目标内存块中。
示例:
#include
#include
int main() {
char src[100] = "hello world";
char dest[100];
printf("复制前的字符串为:%s\n", src);
memcpy(dest, src, sizeof(src));
printf("复制后的字符串为:%s\n", dest);
return 0;
}
输出结果:
复制前的字符串为:hello world
复制后的字符串为:hello world
memmove函数
memmove函数与memcpy函数类似,但是可以处理内存块重叠的情况,它的原型如下:
void *memmove(void *dest, const void *src, size_t n);
其中,dest是目标内存块的指针,src是源内存块的指针,n是要复制的字节数。该函数将源内存块中的内容复制到目标内存块中,可以处理内存块重叠的情况。
示例:
#include
#include
int main() {
char str[100] = "hello world";
printf("反转前的字符串为:%s\n", str);
memmove(str + 3, str, 5);
printf("反转后的字符串为:%s\n", str);
return 0;
}
输出结果:
反转前的字符串为:hello world
反转后的字符串为:helhelrld
calloc函数
calloc函数与malloc函数类似,但是会将申请到的内存块清零,它的原型如下:
void *calloc(size_t nmemb, size_t size);
其中,nmemb是要申请的内存块数量,size是每个内存块的大小,单位是字节。该函数返回一个指向申请到的内存块的指针,如果申请失败,则返回NULL。
示例:
#include
#include
int main() {
int *p = (int *)calloc(5, sizeof(int));
if (p != NULL) {
for (int i = 0; i < 5; i++) {
printf("p[%d]=%d\n", i, p[i]);
}
free(p);
} else {
printf("申请内存失败\n");
}
return 0;
}
输出结果:
p[0]=0
p[1]=0
p[2]=0
p[3]=0
p[4]=0
C语言中,野指针、空指针和悬浮指针是指针的一些特殊情况,它们的区别和示例如下:
野指针是指没有被初始化或者已经被释放的指针,它指向的内存地址是不确定的,可能是一个随机的值,也可能是一个已经被释放的内存地址。使用野指针会导致程序崩溃或者出现其他不可预测的错误。
示例:
#include
int main() {
int *p;
printf("野指针的值为:%p\n", p);
*p = 123;
return 0;
}
输出结果:
野指针的值为:0x7f8c7ac02a40
Segmentation fault (core dumped)
在上面的示例中,指针p没有被初始化,它指向的内存地址是不确定的,因此在对它进行解引用操作时,会导致程序崩溃。
空指针是指没有指向任何内存地址的指针,它的值为NULL。空指针可以用来表示指针不指向任何有效的内存地址,可以避免野指针的问题。
示例:
#include
int main() {
int *p = NULL;
if (p != NULL) {
*p = 123;
} else {
printf("指针为空\n");
}
return 0;
}
输出结果:
指针为空
在上面的示例中,指针p被初始化为NULL,它不指向任何有效的内存地址,因此在对它进行解引用操作时,不会导致程序崩溃。
悬浮指针是指指向已经被释放的内存地址的指针,它指向的内存地址已经不再属于当前程序的内存空间,使用悬浮指针会导致程序崩溃或者出现其他不可预测的错误。
示例:
#include
#include
int main() {
int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
*p = 123;
printf("申请到的内存地址为:%p,值为:%d\n", p, *p);
free(p);
printf("内存已释放\n");
printf("悬浮指针的值为:%p\n", p);
*p = 456;
} else {
printf("申请内存失败\n");
}
return 0;
}
输出结果:
申请到的内存地址为:0x7f8c7ac02a40,值为:123
内存已释放
悬浮指针的值为:0x7f8c7ac02a40
Segmentation fault (core dumped)
在上面的示例中,指针p被初始化为申请到的内存地址,然后又被释放了。在释放后,指针p变成了悬浮指针,它指向的内存地址已经不再属于当前程序的内存空间,因此在对它进行解引用操作时,会导致程序崩溃。
在大型C语言项目开发中,设计调试debug日志表框架是非常重要的,可以帮助开发人员快速定位和解决问题。下面是一些设计调试debug日志表框架的建议:
在设计调试debug日志表框架时,需要定义不同的日志级别,以便开发人员可以根据需要选择输出哪些日志信息。常见的日志级别包括:
- DEBUG:用于输出调试信息,通常只在开发阶段使用。
- INFO:用于输出一般信息,例如程序启动、配置文件加载等。
- WARNING:用于输出警告信息,例如文件读写失败、网络连接断开等。
- ERROR:用于输出错误信息,例如内存分配失败、函数调用失败等。
- FATAL:用于输出致命错误信息,例如程序崩溃、系统错误等。
在设计调试debug日志表框架时,需要定义日志的格式,以便开发人员可以快速定位和解决问题。常见的日志格式包括:
- 时间戳:记录日志的时间,以便开发人员可以知道问题发生的时间。
- 日志级别:记录日志的级别,以便开发人员可以根据需要选择输出哪些日志信息。
- 文件名和行号:记录日志所在的文件名和行号,以便开发人员可以快速定位问题。
- 日志内容:记录日志的具体内容,例如函数调用参数、返回值等。
在设计调试debug日志表框架时,需要实现日志输出函数,以便开发人员可以调用该函数输出日志信息。常见的日志输出函数包括:
- printf函数:可以使用printf函数输出日志信息,但是需要手动添加时间戳、日志级别、文件名和行号等信息。
- syslog函数:可以使用syslog函数输出日志信息,它可以自动添加时间戳、日志级别等信息,但是需要在系统中配置syslog服务。
自定义函数:可以根据项目的需要自定义日志输出函数,例如使用第三方日志库、使用自定义的日志格式等。
在设计调试debug日志表框架时,需要提供配置日志级别的接口,以便开发人员可以根据需要选择输出哪些日志信息。常见的配置日志级别的接口包括:
命令行参数
:可以在程序启动时通过命令行参数指定日志级别。
配置文件:可以在配置文件中指定日志级别,例如使用INI文件、XML文件等。
环境变量:可以在系统中设置环境变量,以便指定日志级别。
在设计调试debug日志表框架时,需要考虑日志文件的大小和数量,以免占用过多的磁盘空间。常见的日志轮换方式包括:
总之,在设计调试debug日志表框架时,需要考虑项目的实际需求和开发人员的使用习惯,以便提高开发效率和代码质量
设计一个日志系统时,需要考虑以下几个方面:
日志级别: 定义不同的日志级别,例如 DEBUG、INFO、WARNING、ERROR和FATAL。
日志格式: 定义日志的格式,例如时间戳、日志级别、文件名和行号、日志内容等。
日志输出: 实现日志输出函数,例如使用printf函数输出日志信息。
日志轮换: 实现日志轮换功能,例如按文件大小轮换、按时间轮换、压缩日志文件等。
下面是一个简单的日志系统设计示例,包括日志级别、日志格式、日志输出和日志轮换功能。
#include
#include
#include
#include
#define LOG_LEVEL_DEBUG 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_WARNING 2
#define LOG_LEVEL_ERROR 3
#define LOG_LEVEL_FATAL 4
#define LOG_FILE_SIZE (1024 * 1024 * 10) // 10MB
#define LOG_FILE_COUNT 10
static const char *log_level_str[] = {
"DEBUG",
"INFO",
"WARNING",
"ERROR",
"FATAL"
};
static FILE *log_file = NULL;
static int log_level = LOG_LEVEL_DEBUG;
static int log_file_size = LOG_FILE_SIZE;
static int log_file_count = LOG_FILE_COUNT;
static void log_rotate()
{
char old_file_name[256];
char new_file_name[256];
fclose(log_file);
for (int i = log_file_count - 1; i > 0; i--) {
snprintf(old_file_name, sizeof(old_file_name), "log.%d", i - 1);
snprintf(new_file_name, sizeof(new_file_name), "log.%d", i);
rename(old_file_name, new_file_name);
}
snprintf(new_file_name, sizeof(new_file_name), "log.0");
log_file = fopen(new_file_name, "w");
}
void log_init(int level, const char *file_name)
{
log_level = level;
log_file = fopen(file_name, "w");
}
void log_set_level(int level)
{
log_level = level;
}
void log_set_file_size(int size)
{
log_file_size = size;
}
void log_set_file_count(int count)
{
log_file_count = count;
}
void log_write(int level, const char *file, int line, const char *fmt, ...)
{
if (level < log_level) {
return;
}
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm);
va_list args;
va_start(args, fmt);
char buf[1024];
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
fprintf(log_file, "[%s] [%s:%d] %s\n", log_level_str[level], file, line, buf);
fflush(log_file);
long file_size = ftell(log_file);
if (file_size >= log_file_size) {
log_rotate();
}
}
#define LOG(level, fmt, ...) \
log_write(level, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
int main()
{
log_init(LOG_LEVEL_DEBUG, "log.0");
LOG(LOG_LEVEL_DEBUG, "debug message");
LOG(LOG_LEVEL_INFO, "info message");
LOG(LOG_LEVEL_WARNING, "warning message");
LOG(LOG_LEVEL_ERROR, "error message");
LOG(LOG_LEVEL_FATAL, "fatal message");
return 0;
}
在上面的示例中,定义了日志级别、日志格式、日志输出和日志轮换功能。日志级别使用了宏定义,方便开发人员根据需要选择输出哪些日志信息。日志格式包括时间戳、日志级别、文件名和行号、日志内容等。日志输出使用了log_write函数,它可以根据日志级别输出不同的日志信息。日志轮换使用了log_rotate函数,它可以按文件大小轮换日志文件。