C语言语法篇
结构体基础知识
a.认识结构体
结构体是一些值的集合,这些值称为成员变量.结构体的每个成员可以是不同类型的变量.
1 | //声明一个结构体类型 |
b.结构体自引用
1 | //结构体的自引用 |
c.结构体嵌套使用
1 | struct T |
C语言内存模型
C语言的内存模型是基于物理内存的抽象模型,它将物理内存划分为不同的区域,每个区域有不同的作用和特点。C语言的内存模型主要包括以下几个部分:
代码区:存放程序的指令代码,通常是只读的。(字符串常量也会放这)
数据区:存放程序中已经初始化的全局变量和静态变量。
BSS段:存放未初始化的全局变量和静态变量,通常被初始化为0。
堆区:动态分配内存的区域,由程序员手动管理。(malloc free)
栈区:存放函数的局部变量和函数调用的上下文信息,由编译器自动管理。
在C语言中,变量的存储位置和生命周期由其作用域和存储类别决定。作用域指的是变量在程序中的可见范围,存储类别指的是变量在内存中的存储方式和生命周期。C语言中的存储类别包括自动存储类别、静态存储类别、寄存器存储类别和外部存储类别。
自动存储类别的变量通常存储在栈区,它们的生命周期与函数调用的生命周期相同。静态存储类别的变量通常存储在数据区或BSS段,它们的生命周期与程序的运行周期相同。寄存器存储类别的变量通常存储在CPU的寄存器中,它们的生命周期与函数调用的生命周期相同。外部存储类别的变量通常存储在数据区或BSS段,它们的生命周期与程序的运行周期相同,但它们可以被其他文件访问。
当我们定义一个变量时,它的存储位置和生命周期由其作用域和存储类别决定。下面是一些不同类型变量存储在不同区域的例子:
全局变量:全局变量通常存储在数据区或BSS段中,它们的生命周期与程序的运行周期相同
。例如:
1 | int global_var = 10; // 存储在数据区 |
局部变量:局部变量通常存储在栈区中,它们的生命周期与函数调用的生命周期相同
。例如:
1 | void func() { |
动态分配内存:动态分配内存通常存储在堆区中,由程序员手动管理
。例如:
1 | int* ptr = (int*)malloc(sizeof(int)); // 存储在堆区 |
字符串常量:字符串常量通常存储在代码区中,它们是只读的
。例如:
1 | char* str = "Hello, world!"; // 存储在代码区 |
需要注意的是,这些变量的存储位置和生命周期可能会因为编译器、操作系统和硬件平台的不同而有所差异。
C基本数据类型
C语言中基本变量的类型及字节长度如下:
类型 | 字节长度 |
---|---|
char | 1 |
short | 2 |
int | 4 |
long | 4或8 |
float | 4 |
double | 8 |
下面给出数组、字符数组、链表、队列、栈、图的数据结构用C语言定义的示例:
1 | //数组 |
C语言中静态局部变量和局部变量的区别是什么?
C语言中,静态局部变量和普通局部变量的区别主要有以下几点:
存储位置不同
静态局部变量存储在静态数据区,而普通局部变量存储在栈中。静态数据区是程序在编译时就分配好的一块内存,它在程序运行期间一直存在,直到程序结束才被释放。而栈是程序在运行时动态分配的一块内存,它的生命周期与函数调用的生命周期相同。
生命周期不同
静态局部变量的生命周期与程序的生命周期相同,即在程序运行期间一直存在,直到程序结束才被释放。而普通局部变量的生命周期与函数调用的生命周期相同,即在函数调用结束时被释放。
初值不同
静态局部变量在第一次使用之前会被自动初始化为0或者NULL,而普通局部变量不会被自动初始化,它们的初值是未定义的。
可见性不同
静态局部变量的作用域仅限于定义它的函数内部,但是它的值在函数调用结束后仍然保持不变,下次调用该函数时仍然可以使用。而普通局部变量的作用域也仅限于定义它的函数内部,但是它的值在函数调用结束后就被销毁了,下次调用该函数时需要重新初始化。
需要注意的是,静态局部变量和普通局部变量的使用场景不同。静态局部变量适用于需要在函数调用之间保持状态的情况,而普通局部变量适用于临时存储数据的情况。在使用静态局部变量时,需要注意它的生命周期和可见性,避免出现意外的错误。
C语言关于字符串的处理函数
C语言中关于字符串赋值、比较、反转、初始化、拼接、截取的函数如下:
字符串赋值 strcpy
字符串赋值可以使用strcpy函数,它的原型如下:
1 | char strcpy(char dest, const char *src); |
其中,dest是目标字符串的指针,src是源字符串的指针。该函数将源字符串复制到目标字符串中,并返回目标字符串的指针。
字符串比较 strcmp
字符串比较可以使用strcmp函数,它的原型如下:
1 | int strcmp(const char s1, const char s2); |
其中,s1和s2是要比较的两个字符串的指针。该函数比较两个字符串的大小,如果s1小于s2,则返回一个负数;如果s1等于s2,则返回0;如果s1大于s2,则返回一个正数。
字符串反转 strrev
字符串反转可以使用strrev函数,它的原型如下:
1 | char strrev(char str); |
其中,str是要反转的字符串的指针。该函数将字符串中的字符顺序反转,并返回反转后的字符串的指针。
字符串初始化 memset
字符串初始化可以使用memset函数,它的原型如下:
1 | void memset(void s, int c, size_t n); |
其中,s是要初始化的字符串的指针,c是要初始化的字符,n是要初始化的字符数。该函数将字符串中的每个字符都设置为指定的字符。
字符串拼接 strcat
字符串拼接可以使用strcat函数,它的原型如下:
1 | char strcat(char dest, const char *src); |
其中,dest是目标字符串的指针,src是要拼接的字符串的指针。该函数将源字符串拼接到目标字符串的末尾,并返回目标字符串的指针。
字符串截取 strncpy
字符串截取可以使用strncpy函数,它的原型如下:
1 | char strncpy(char dest, const char *src, size_t n); |
其中,dest是目标字符串的指针,src是源字符串的指针,n是要截取的字符数。该函数将源字符串中的前n个字符复制到目标字符串中,并返回目标字符串的指针。
需要注意的是,上述函数的使用需要注意字符串的长度和内存分配,避免出现缓冲区溢出等问题。在使用字符串函数时,需要仔细阅读函数的文档,了解函数的使用方法和注意事项。
计算字符串长度 strlen函数
strlen函数用于计算字符串的长度,它的原型如下:
1 | size_t strlen(const char *s); |
其中,s是要计算长度的字符串的指针。该函数返回字符串的长度,不包括字符串末尾的空字符。
示例:
1 |
|
查找指定字符 strchr函数
strchr函数用于在字符串中查找指定字符的位置,它的原型如下:
1 | char strchr(const char s, int c); |
其中,s是要查找的字符串的指针,c是要查找的字符。该函数返回指向第一个匹配字符的指针,如果未找到匹配字符,则返回NULL。
示例:
1 |
|
查找子串 strstr函数
strstr函数用于在字符串中查找指定子串的位置,它的原型如下:
1 | char strstr(const char haystack, const char *needle); |
其中,haystack是要查找的字符串的指针,needle是要查找的子串的指针。该函数返回指向第一个匹配子串的指针,如果未找到匹配子串,则返回NULL。
示例:
1 |
|
格式化数据写入 sprintf函数
sprintf函数用于将格式化的数据写入字符串中,它的原型如下:
1 | int sprintf(char str, const char format, ...); |
其中,str是要写入的字符串的指针,format是格式化字符串,…是可变参数列表。该函数将格式化的数据写入字符串中,并返回写入的字符数。
示例:
1 |
|
需要注意的是,在使用sprintf函数时,需要确保目标字符串的长度足够大,以避免缓冲区溢出等问题。
以上字符串处理函数的头文件为<string.h>
C语言中野指针和空指针,悬浮指针
C语言中,动态申请内存、释放内存、保证内存安全的函数如下:
动态申请内存 malloc
动态申请内存可以使用malloc函数,它的原型如下:
1 | void *malloc(size_t size); |
其中,size是要申请的内存大小,单位是字节。该函数返回一个指向申请到的内存块的指针,如果申请失败,则返回NULL。
示例:
1 |
|
释放内存 free
释放内存可以使用free函数,它的原型如下:
1 | void free(void *ptr); |
其中,ptr是要释放的内存块的指针。该函数将指定的内存块释放回系统,以便其他程序可以使用。
示例:
1 |
|
需要注意的是,在使用free函数释放内存时,需要确保指定的内存块是通过malloc函数申请的,否则可能会导致程序崩溃或者内存泄漏等问题。
保证内存安全
为了保证内存安全,C语言提供了一些函数,可以检查内存访问是否越界,避免出现缓冲区溢出等问题。常用的内存安全函数如下:
memset函数:用于将一段内存块清零,可以避免出现敏感信息泄露的问题。
memcpy函数:用于将一段内存块复制到另一段内存块中,可以避免出现内存访问越界的问题。
memmove函数:与memcpy函数类似,但是可以处理内存块重叠的情况。
calloc函数:与malloc函数类似,但是会将申请到的内存块清零,可以避免出现敏感信息泄露的问题。
当然,下面给出一些常用的C语言保证内存安全函数的用法示例:
memset函数
memset函数用于将一段内存块清零,它的原型如下:
1 | void memset(void s, int c, size_t n); |
其中,s是要清零的内存块的指针,c是要设置的值,n是要清零的字节数。该函数将指定的内存块中的每个字节都设置为指定的值。
示例:
1 |
|
memcpy函数
memcpy函数用于将一段内存块复制到另一段内存块中,它的原型如下:
1 | void memcpy(void dest, const void *src, size_t n); |
其中,dest是目标内存块的指针,src是源内存块的指针,n是要复制的字节数。该函数将源内存块中的内容复制到目标内存块中。
示例:
1 |
|
memmove函数
memmove函数与memcpy函数类似,但是可以处理内存块重叠的情况,它的原型如下:
1 | void memmove(void dest, const void *src, size_t n); |
其中,dest是目标内存块的指针,src是源内存块的指针,n是要复制的字节数。该函数将源内存块中的内容复制到目标内存块中,可以处理内存块重叠的情况。
示例:
1 |
|
calloc函数
calloc函数与malloc函数类似,但是会将申请到的内存块清零,它的原型如下:
1 | void *calloc(size_t nmemb, size_t size); |
其中,nmemb是要申请的内存块数量,size是每个内存块的大小,单位是字节。该函数返回一个指向申请到的内存块的指针,如果申请失败,则返回NULL。
示例:
1 |
|
C语言中野指针,空指针和悬浮指针的区别和示例
C语言中,野指针、空指针和悬浮指针是指针的一些特殊情况,它们的区别和示例如下:
野指针
野指针是指没有被初始化或者已经被释放的指针,它指向的内存地址是不确定的,可能是一个随机的值,也可能是一个已经被释放的内存地址。使用野指针会导致程序崩溃或者出现其他不可预测的错误。
示例:
1 |
|
在上面的示例中,指针p没有被初始化,它指向的内存地址是不确定的,因此在对它进行解引用操作时,会导致程序崩溃。
空指针
空指针是指没有指向任何内存地址的指针,它的值为NULL。空指针可以用来表示指针不指向任何有效的内存地址,可以避免野指针的问题。
示例:
1 |
|
在上面的示例中,指针p被初始化为NULL,它不指向任何有效的内存地址,因此在对它进行解引用操作时,不会导致程序崩溃。
悬浮指针
悬浮指针是指指向已经被释放的内存地址的指针,它指向的内存地址已经不再属于当前程序的内存空间,使用悬浮指针会导致程序崩溃或者出现其他不可预测的错误。
示例:
1 |
|
在上面的示例中,指针p被初始化为申请到的内存地址,然后又被释放了。在释放后,指针p变成了悬浮指针,它指向的内存地址已经不再属于当前程序的内存空间,因此在对它进行解引用操作时,会导致程序崩溃。
设计Debug日志系统
在大型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函数输出日志信息。
日志轮换
实现日志轮换功能,例如按文件大小轮换、按时间轮换、压缩日志文件等。
日志系统设计示例
下面是一个简单的日志系统设计示例,包括日志级别、日志格式、日志输出和日志轮换功能。
1 |
|
在上面的示例中,定义了日志级别、日志格式、日志输出和日志轮换功能。日志级别使用了宏定义,方便开发人员根据需要选择输出哪些日志信息。日志格式包括时间戳、日志级别、文件名和行号、日志内容等。日志输出使用了log_write函数,它可以根据日志级别输出不同的日志信息。日志轮换使用了log_rotate函数,它可以按文件大小轮换日志文件。
- 本文作者: CoderSong
- 本文链接: https://jack-song-gif.github.io/2024/02/04/c语言入门总篇/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!