C语言从“hello word”到深入【第五节·提升部分及知识点总结】

目录

内存管理

枚举类型

结构体类型

共用体(联合体)--union


5.1内存管理

内存管理在编程中,尤其是像C语言这样的低级语言中,扮演着至关重要的角色。它涉及到如何有效地分配、使用和释放计算机内存资源,以确保程序的性能和稳定性。以下是对内存管理的详细探讨:

一、内存管理的概念

内存管理是指程序运行时对计算机主存(RAM)的管理过程,包括内存的分配、使用和释放。在C语言中,由于它不提供自动内存管理机制(如垃圾回收),因此程序员需要手动进行内存管理。

二、内存管理的重要性
  1. 性能优化:手动内存管理允许程序员根据程序的具体需求精确控制内存的使用,从而提高程序的执行效率。
  2. 资源利用:有效的内存管理可以减少内存泄漏和碎片化,确保内存资源得到充分利用。
  3. 程序稳定性:通过避免访问非法内存(如已释放的内存或未初始化的内存),内存管理可以提高程序的稳定性和可靠性。
三、C语言中的内存管理

C语言中的内存管理主要包括静态内存分配、栈内存分配和动态内存分配三种方式。

  1. 静态内存分配
    • 在编译时进行,为全局变量和静态变量分配内存。
    • 内存分配和释放的效率高,但内存使用不灵活。
  2. 栈内存分配
    • 在程序运行时进行,为函数内部的局部变量分配内存。
    • 分配和释放由系统自动完成,具有后进先出(LIFO)的特性。
    • 内存空间有限,不适合分配大内存。
  3. 动态内存分配
    • 在程序运行时根据需要进行内存分配和释放。
    • 使用malloc()calloc()realloc()等函数进行内存分配,使用free()函数释放内存。
    • 内存使用灵活,但管理复杂,容易出现内存泄漏等问题。
四、内存管理函数

    1.malloc()

函数原型:
#include <stdlib.h>
void *malloc(size_t size);

        用于分配指定大小的内存块。
        返回值是指向分配的内存块的指针,如果分配失败则返回NULL。
        分配的内存块是未初始化的,其内容不确定。

  1. calloc()
    • 类似于malloc(),但分配的内存块会被初始化为零。
    • 第一个参数是元素数量,第二个参数是每个元素的大小。
  2. realloc()
    • 用于调整已分配内存块的大小。
    • 如果增加大小,可能会将原有数据移动到新的内存位置;如果减小大小,则可能不移动。
    • 返回指向新内存块的指针,如果分配失败则返回NULL。
  3. free()
    • 用于释放之前通过malloc()、calloc()或realloc()分配的内存块。
    • 释放后的内存块不再可用,再次访问将导致未定义行为。
    • malloc与free配对使用,编译器不会对动态内存释放,需要程序员手动释放;
五、内存管理的注意事项
  1. 避免内存泄漏:确保所有分配的内存都在不再需要时被释放。
  2. 防止野指针:释放内存后,将指针置为NULL,避免悬空指针。
  3. 检查分配结果:在使用malloc()、calloc()或realloc()后,检查返回值是否为NULL,以判断内存分配是否成功。
  4. 合理管理堆内存:堆内存是动态分配的,使用时要谨慎,避免内存碎片化。

综上所述,内存管理在C语言中是一个复杂而重要的任务。通过合理的内存管理策略,可以提高程序的性能和稳定性,确保程序的正确执行。

5.12关于野指针问题

野指针(Wild Pointer)是一种在编程中常见的危险情况,它指的是指针指向的位置是不可知的、随机的、不正确的或没有明确限制的。野指针的出现往往是由于编程疏忽或不当的内存管理造成的。以下是关于野指针的详细解释:
 

一、野指针的产生原因

指针变量未初始化:

当指针变量被创建时,如果没有进行初始化,它的值是随机的,可能指向任意的内存地址。此时,如果尝试通过这个指针去读取或写入数据,可能会导致程序崩溃或其他不可预期的行为。

指针释放后未置空:

当指针所指向的内存被释放(如使用free或delete)后,如果没有将指针置为NULL,那么这个指针就变成了野指针。尽管内存已被释放,但指针仍然指向那块内存,这可能导致在后续的程序中错误地访问该内存。

指针操作超越变量作用域:

例如,返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放,导致返回的指针成为野指针。

指针越界访问:

当指针超出了它所指向的数据结构(如数组)的边界时,就会发生越界访问。越界后的指针可能指向未知的内存区域,成为野指针。

其他不当操作

如对象复制过程中未按预期管理内存,导致多个对象指向同一块已释放的内存等。

二、野指针的危害

触发段错误:

当野指针指向一个不可访问的内存地址时,尝试对其进行解引用操作(如读取或写入)可能会导致段错误(Segmentation Fault),使程序崩溃。

数据损坏:

如果野指针恰好指向了程序中的有效数据区域,那么通过该指针进行的读写操作可能会破坏这些数据,导致程序行为异常。

安全漏洞:

在安全敏感的应用程序中,野指针可能导致安全漏洞,如信息泄露、越权访问等。

三、如何避免野指针

初始化指针:

在定义指针时,应始终将其初始化为NULL或指向有效的内存地址。

释放后置空:

在释放指针所指向的内存后,应立即将指针置为NULL,防止其成为野指针。

检查指针有效性:

在使用指针之前,应检查其是否为NULL或指向有效的内存地址。

避免越界访问:

确保指针操作不会超出其指向的数据结构的边界。

合理管理内存:

在对象复制、函数返回等操作中,应合理管理内存,避免产生野指针。

5.13内存管理中堆和栈的区别

在C语言中,内存管理是一个重要的概念,主要涉及两个方面:堆(Heap)和栈(Stack)。

栈与堆的区别

分配方式:栈内存由编译器自动分配和释放,而堆内存需要程序员显式地分配和释放。

生命周期:栈内存的生命周期与函数调用的生命周期相关,而堆内存的生命周期由程序员控制。

大小与限制:栈内存的大小通常有限,并且由操作系统管理;而堆内存的大小通常较大,但受限于系统的可用内存。

安全性:栈内存相对更安全,不容易出现内存泄漏或野指针等问题;而堆内存的管理更复杂,更容易出现错误。

5.2枚举类型

枚举类型(Enumeration Type),简称枚举,是一种特殊的数据类型,它让变量成为预先定义好的一组常量中的一个。在很多编程语言中,枚举类型被用来定义变量,使得这些变量只能赋值为特定的几个值之一。

枚举的使用场景非常广泛,比如在编程时表示星期、月份、方向、状态等有限集合的情况。使用枚举可以增加代码的可读性和维护性。

枚举的定义

不同的编程语言中枚举的定义方式可能有所不同。以下是一些常见编程语言中枚举的定义方式:

在C或C++中,枚举的定义使用enum关键字。

枚举类型定义形式:
enum 枚举名{枚举成员列表};
举例:
enum Weekday {  
    Monday,  
    Tuesday,  
    Wednesday,  
    Thursday,  
    Friday,  
    Saturday,  
    Sunday  
};
枚举的优点
  1. 类型安全:使用枚举可以保证变量只能取在定义时列举出的几个值,从而防止了无效值的赋予。
  2. 便于阅读和维护:枚举值具有描述性,使得代码更加易于理解和维护。
  3. 便于扩展:在需要添加新的值时,只需简单地在枚举类型中添加即可,无需修改使用枚举的代码。

    在枚举类型中,声明的第一个枚举成员的默认值为零。以后的枚举成员值是将前一枚举成员的值加1。增加后的值必须在整型可表示的值的范围内;也可以在定义枚举类型时,为枚举成员显示赋值,允许多个枚举成员有相同的值。没有显示赋值的枚举成员的值,总是前一枚举成员的值+1;
    示例:
    enum TIME
    {
       morning = 1;
       afternoon,
       evening = 1;
    };
    morning的值为1;afternoon的值为2;evening的值为1;
    

    5.3结构体

      在C语言中,结构体(Structures)是一种复合数据类型,在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行管理的。允许你将不同类型的数据项组合成一个单一的类型。结构体用于表示具有多个属性的复杂数据类型,例如,一个学生信息可能包括学生的姓名、学号、年龄和成绩等多个属性,这些属性就可以通过定义一个结构体来组织和管理。

 5.31定义结构体

结构体通过struct关键字来定义。下面是一个定义学生信息结构体的例子:

struct Student {  
    char name[50]; // 学生姓名  
    int id;        // 学生学号  
    int age;       // 学生年龄  
    float score;   // 学生成绩  
};

这个结构体创建了一个新的数据类型struct Student,其中包含四个成员:name、id、age和score。

使用结构体

定义了结构体之后,就可以声明该类型的变量了。声明结构体变量的方式有两种:

1.在定义结构体时直接声明变量;

struct Student {  
    char name[50];  
    int id;  
    int age;  
    float score;  
} stu1, stu2; // 同时声明了两个Student类型的变量stu1和stu2

2.先定义结构体再声明变量;

struct Student {  
    char name[50];  
    int id;  
    int age;  
    float score;  
};  
 
struct Student stu1, stu2; // 声明了两个Student类型的变量stu1和stu2

声明结构体变量后可以直接使用结构体中的成员,包括修改或访问结构体中的元素;访问时使用声明的变量名+"."可调用结构体中成员;如:stu1.id;stu2.age;

结构体数组

结构体数组是一种非常有用的数据结构,它允许我们将多个结构体变量组合成一个数组。每个元素都是一个结构体,包含了结构体中定义的所有成员。

1.先定义结构体类型,再用它定义结构体数组;

struct 结构体名

{

类型 成员名;

类型 成员名;

...

};

struct 结构体名 数组名[元素个数];

示例程序:
#define N 64
struct Student 
{  
    char name[50];  
    int id;  
    int age;  
    float score;  
};
struct Student e[10];

2.定义结构体的同时,定义结构体数组;

struct 结构体名

{

类型 成员名;

类型 成员名;

...

} 数组名[元素个数];

示例程序:
#define N 64
struct Student 
{  
    char name[50];  
    int id;  
    int age;  
    float score;  
} e[10];

3.直接定义结构体数组;

struct 

{

类型 成员名;

类型 成员名;

...

} 数组名[元素个数];

示例程序:
#define N 64
struct
{  
    char name[50];  
    int id;  
    int age;  
    float score;  
} e[10];
结构体数组初始化

结构体数组在定义时也可以进行初始化,并且与结构体变量的初始化规定相同。

结构体初始化一般形式:

struct 结构体名

{

类型 成员名;

类型 成员名;

......

};

struct 结构体名 数组名[元素个数]={初始化列表};

——————————————————————————————

struct 结构体名

{

类型 成员名;

类型 成员名;

......

}数组名[元素个数]={初始化列表};

——————————————————————————————

struct 

{

类型 成员名;

类型 成员名;

......

}数组名[元素个数]={初始化列表};

结构体指针

在C语言中,结构体指针是一个指向结构体变量的指针。它通常用于动态内存分配、传递结构体到函数以及访问结构体成员。可以设定一个指针变量用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。

定义结构体指针:首先,你需要定义一个结构体类型,然后可以定义一个指向该结构体的指针。例如:

struct Student {
    char name[50];
    int age;
    float score;
};

struct Student *ptr;

在以上代码中,ptr是一个指向Student结构体的指针;

使用结构体指针:要使指针指向一个具体的结构体变量,你可以使用地址运算符&。例如:

struct Student s1;
ptr = &s1;

ptr指向了s1变量

访问结构体成员:可以使用箭头运算符->来通过结构体指针访问结构体的成员。例如:

ptr->age = 20;  // 设置s1的age成员为20
printf("%s\n", ptr->name);  // 打印s1的name成员

动态分配内存:结构体指针常用于动态分配内存。例如:

struct Student *ptr = malloc(sizeof(struct Student));
if (ptr != NULL) {
    strcpy(ptr->name, "John Doe");
    ptr->age = 30;
    ptr->score = 92.5;
}

注意:在使用完动态分配的内存后,需要及时使用free(ptr)释放;

作为函数参数:结构体指针可以作为函数参数传递,这样可以在函数内部修改结构体的内容

void printStudent(struct Student *s) {
    printf("Name: %s\n", s->name);
    printf("Age: %d\n", s->age);
    printf("Score: %.2f\n", s->score);
}
位域

在C语言中,位域(Bit-field)是一种特殊的数据成员,它允许在结构体(struct)中以位为单位来定义变量的长度。位域主要用于节省内存,特别是当需要存储多个布尔值或小的枚举值时。

位域在结构体中定义

struct {
    type [member_name] : width;
};
type:位域的类型,通常是unsigned int,也可以是int(不过不推荐,因为位域的行为对于有符号整数是未定义的)。
member_name:位域的名称。
width:位域的宽度,即所需的位数。

例:
struct Flags {
    unsigned int a : 1;  // 占用1位
    unsigned int b : 1;  // 占用1位
    unsigned int c : 3;  // 占用3位
    unsigned int d : 5;  // 占用5位
};
整个Flags结构体的大小将小于等于一个unsigned int的大小,因为所有位域加起来不会超过unsigned int的大小。
访问位域
struct Flags f;
f.a = 1;  // 设置位域a
f.b = 0;  // 清除位域b
f.c = 3;  // 设置位域c为3(二进制011)

位域在实际使用中较为复杂,一般不建议使用;

本章完!
下一章主要为c语言数据结构部分的知识点,下一章会讲述数据结构的中的链表,栈,队列,排序,树相关的知识总结!

有问题或者错误反馈请联系邮箱:z1600306511@163.com

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值