学习c++ Part03

本文介绍了C++中的动态内存管理,包括静态和动态空间的申请与释放,重点讨论了结构体的深拷贝和浅拷贝概念,以及如何避免内存泄漏。同时,讲解了字符串处理函数如strlen、strcpy等的使用,链表的基本操作,以及枚举的应用。
摘要由CSDN通过智能技术生成

前言

日期:2023年7月3日 下午开始~平时还有论文和实验代码要看 进度缓慢 还没到正经内容。

1.动态空间申请

1.1 静态空间申请

事先知道大小,分配在栈区或者全局区

int num = 100;
int arr[4] = {0};

1.2 动态分配

关键字:

  • new 申请堆区空间
  • delete 释放空间
  • 释放申请是数组空间 需要 加上[]
  • new delete 是成对使用的
    使用实例:
void test08()
{
    // new delete 动态申请空间
    //动态申请int
    int *p = new int(10);
    cout<<*p<<endl;
    //释放
    delete p;
/*------------------------------------------*/
    //动态申请数组
    int *arr = new int[4]{10,20,30,40};
    for (int i = 0; i < 4; ++i) {
        cout<<*(arr+i)<<endl;
    }
    //释放
    delete [] arr;
}

2.字符串处理函数

头包含

#include<string.h>

各种操作:

#include<string.h>
//测量字符长度
//size_t strlen(const char *s); // s指测量字符串的首元素地址 '\0'停止
char str1[128]="hello";
strlen(str1);//5

//字符串拷贝
/*
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n); dest:目的空间地址
src:原字符串的首元素地址
*/
char dst[128]="";
char src[]="hello\0world";
strcpy(dst,src);
cout<<dst<<endl;//hello

//字符串追加
/*
char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n); 将src指向的字符串 追加到 dest指向的字符串尾部
*/
char dst[128]="hello";
char src[]="world";
strcat(dst,src);
cout<<dst<<endl;//helloworld

//字符串比较
/*
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
返回值:
比较的ASCII值
>0 s1字符串 > s2字符串 
<0 s1字符串 < s2字符串
==0 s1字符串==s2字符串
*/

3.结构体

定义&使用:

#include<string.h>
sturct Student{
	int num;
	char name[32];
}
Student lucy; //不初始化通过lucy.num 获取到的是随机值
Student zhang = {01,"zhang"} //按照顺序初始化
//name成员时数组名 位符号常量 不允许用=给name赋值 
//zhang.name = "xiaozhang";//错误 
strcpy(zhang.name, "xiaozhang");
// 获取值的方式
//zhang.num zhang.name

清空结构体变量:

//void *memset(void *_Dst,int _Val,size_t _Size); 
//将地址从_Dst开始 长度位_Size的所有字节赋值位_Val
memset(&zhang,0,sizeof(zhang));

结构体嵌套:

struct Address{
	char province[32];
	char region[32];
	int num;
}
struct Student{
	int num;
	char name[32];
	Address add;
}

结构体数组:

Student stus[3] = {01,"zhangxueyou",02,"zhangtielin",03,"zhangguorong"};
//stus[0] 与数组使用类似

结构体指针变量:

struct Student{
int num;
char name[32];
}
Student zhang = {01,"zhang"};
Student *stu = &zhang;
//通过指针访问成员变量
//stu->num
//stu->name

结构体指针成员,指针文字常量区(只读)

struct stu{
	int num;
	char *name;
}
Stu zhang;
zhang.num = 100;
zhang.name = new char[32];
strcpy(zhang.name, "xiaozhang");
cout<<zhang.num<<" "<<zhang.name<<endl;
delete [] zhang.name;

3.1 结构体的浅拷贝

整体赋值就是浅拷贝,结构体中没有指针成员变量的时候,浅拷贝不会带来任何问题;如果结构体中有指针成员变量时,会出现多次释放堆区空间的问题

struct stu{
	int num;
	char *name;
}
stu zhang;
zhang.num = 100;
zhang.name = new char[32];
strcpy(zhang.name, "xiaozhang");
stu wang;
wang = zhang;
//操作完毕后多次释放空间
delete [] zhang.name;
delete [] wang.name;

3.2 结构体的深拷贝

如果结构体中有指针成员,尽量使用深拷贝
深拷贝就是为结构体中的指针成员分配独立的空间,然后再进行内容拷贝

struct stu{
	int num;
	char *name;
}
stu zhang;
zhang.num = 100;
zhang.name = new char[32];
strcpy(zhang.name, "xiaozhang");
stu wang;
wang.num = zhang.num;
wang.num = new char[32];
strcpy(wang.name, zhang.name);
//操作完毕后多次释放空间
delete [] zhang.name;
delete [] wang.name;

3.3 结构体变量在堆区 结构体指针成员也指向堆区(先释放成员,再释放结构体)

struct stu {
	int num;
    char *name;
};
//结构体在堆区
stu *p = new stu;
//结构体中指针成员指向堆区 
p->name = new char[32];
//赋值
p->num = 100;
strcpy(p->name, "hello world");
cout<<p->num<<" "<<p->name<<endl;
//释放空间
delete [] p->name;//先释放成员指向 
delete p;//再释放结构体

3.4 结构体的对齐规则

对齐规则是按照字节来的 char1 short2 int4
自动对齐:

  • 一行多少字节,由结构体中最大的基本类型决定
  • 成员偏移量:最大成员的类型的正数倍
  • 结构体的总大小=分配单位整数倍
struct Data {
    char a;
    short b;
    int c;
    char d;
    short e;
};

结构体内存分布图:
在这里插入图片描述
强制对齐:

#pragma pack (value)时的指定对齐值value。
  • 分配单位:min(最大基本类型,value)
  • 成员偏移量= 成员自身类型的整数倍
  • 结构体总大小:分配单位的整数倍
    在这里插入图片描述
    在这里插入图片描述

3.5 结构体的位域(了解 放几张图 可以看看 通常用在嵌入式中)

位域是以二进制位为单位的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用案例:
在这里插入图片描述

3.6 共用体union

结构体:所有成员拥有独立的空间
共用体:所有成员共享同一块空间
共用体空间,由最大的成员类型决定

union data{
	char a;
	short b;
	int c;
}
void test01(){
	Data ob;
    ob.a = 10;
    ob.b = 20;
    ob.c = 30;
    cout<<ob.a+ob.b+ob.c<<endl;//90==30+30+30
}
void test02(){
	Data ob;
ob.c = 0x01020301;
ob.b = 0x0102;
ob.a = 0x01;
cout<<ob.a+ob.b+ob.c<<endl;//0x01020203
}

3.7 枚举 enum(需要了解,可以测试)

enum province{JIANGSU,ZHEJIANG,ANHUI,HENAN};//0,1,2,3

4.链表

每个节点通过指针域 保存下一个节点的位置 达到逻辑上连续
链表数组优缺点:
数组:

  • 优缺点:查询效率高,插入效率低
    链表
  • 优缺点:插入效率高,查询效率低

静态链表的定义:

struct p{
	//数据域
	int num;
	char name[32];
	//指针域
	struct p *next;
}

p p1 = {1, "aaa", NULL}; 
p p2 = {2, "bbb", NULL}; 
p p3 = {3, "ccc", NULL}; 
p p4 = {4, "ddd", NULL}; 
p p5 = {5, "eee", NULL};
//定义一个数据域为空的链表头
p *head = &p1;
p1.next = &p2; 
p2.next = &p3; 
p3.next = &p4; 
p4.next = &p5; 
p5.next = NULL;

//遍历
p *pd = head; // 添加首元节点
while(pd->next != NULL){
	cout<<pd->num<<" "<<pd->name<<endl;
	pd = pd->next; 
}
4.1 为什么要预设首元结点,只想head节点?
  1. 便于首元结点的处理:增加头结点后,首元结点的地址保存在头结点的指针域中,则对链表的第一个元素的操作和其他元素相同,无需进行特殊处理。(若单链表不带头结点,则首元结点无前驱结点,在其前插入结点和删除该结点操作复杂些。)

  2. 便于空表和非空表的统一处理:增加头结点后,无论链表是否为空,头指针都是指向头结点的非空指针

  • 注:首元结点是指链表中存储线性表中第一个数据元素a1的结点(头结点的下一个结点)。为了操作方便,通常在链表的首元结点之前附设一个结点(头结点)。
4.2 动态链表的插入、删除
struct stu{
	//数据域
	int num;
	char name[32];
	//指针域
	stu *next;
}

插入:

# if 0
//插入节点 头插
stu *linkInsert(stu *head, stu tmp) {
    //开辟堆空间
    stu *pi = new stu;
    *pi = tmp;
    pi->next = NULL; //下个节点指向空
    //判断头节点是否为空
    if (head == NULL) {
        //直接令插入节点为头结点
        head = pi;
    } else {
        //头结点不为空
        pi->next = head;
        head = pi;
    }
    return head;
}
#endif
# if 0
//插入节点 尾插
stu *linkInsert(stu *head, stu tmp) {
    //开辟堆空间
    stu *pi = new stu;
    *pi = tmp;
    pi->next = NULL; //下个节点指向空
    //判断头节点是否为空
    if (head == NULL) {
        //直接令插入节点为头结点
        head = pi;
    } else {
        //找到尾结点
        stu *pb = head; // 必须这么做 保留前面的数据
        while (pb->next != NULL) {
            pb = pb->next;
        }
        pb->next = pi;
    }
    return head;
}
#endif

#if 1
    //有序插入
stu *linkInsert(stu *head, stu tmp) {
    stu *pi = new stu;
    *pi = tmp;
    pi->next = NULL;
    //判空
    if (head == NULL) {
        head = pi;
    } else {
        //链表存在
        stu *pf = head;
        stu *pb = head;
        while (pi->num > pb->num && pb->next != NULL) {
            //保存pb节点
            pf = pb;
            //pd像后移动
            pb = pb->next;
        }
            //判断插入点位置
            if(pi->num <= pb->num){
                if(pb == head){
                	//pb->next == NULL 的情况
                    //最开头插入
                    pi->next = head;
                    head = pi;
                }else{
                    //中间插入(pf&pd之间搭桥)
                    pf->next = pi;
                    pi->next = pb;
                }
            }else{
                //尾部插入(值最大)
                pb->next = pi;
            }
    }
    return head;
}
#endif

删除节点

stu *linkDelete(stu *head, int num) {
    //判空
    if (head == NULL) {
        cout << "link not exit" << endl;
    }
    stu *pf = head;
    stu *pb = head;
    //遍历链表
    while (pb->num != num && pb->next != NULL) {
        pf = pb;
        pb = pb->next;
    }

    //找到删除点
    if (pb->num == num) {
        if (pb == head) {
            //删除头节点
            head = pb->next;
        } else {
            //不是头结点
            pf->next = pb->next;
        }
        delete pb;
    } else {
        cout << "未找该节点" << endl;
    }
    return head;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值