c++笔记(指针、结构体、链表)

c + + 学习笔记(指针、结构体、链表)

一、指针

指针就是用来存储地址的类型

int main()
{
    int num = 100;
    int* p = #
    cout << "num的地址是:" << p<<endl;
    cout << "num的值是:" << *p;
}

在这里插入图片描述

二、指针与数组

1 数字数组

数组名就是数组首地址

int * arr[4] 这是一个指针数组,本质是个数组,每一个元素是一个指针。

下面例子里arr每一个元素存储的是arr0的每一个对应的元素的地址

//数字数组练习
void test02() {
    int arr0[4] = { 10,20,30,40 };
    //声明一个指针数组
    int* arr[4];
    //输出结果为16,因为指针大小为4B(32位平台)
    cout << sizeof(arr) << endl;
    //计算数组大小
    int n = sizeof(arr) / sizeof(arr[0]);
    //将四个数字的地址赋值给指针数组
    for (int i = 0; i < n; i++)
    {
        arr[i] = &arr0[i];
        //输出地址
        cout << arr[i] << "->";
        //输出内容
        cout << *arr[i] << endl;
    }
}

在这里插入图片描述

2 字符串数组

这里声明的arr是指针数组,每一个元素存贮的都是一个地址,所以后面{}里面的元素都进行了(char *)的强制转换。

arr[i] 代表的是第i个元素的首地址

arr[i]+n 代表的是 第i个元素 从第n个字符开始的地址

*(arr[i] + n) 代表的是 第i个元素 从第n个字符的具体的值

//字符串数组练习;
void test01() {
    /*这里使用强制转换是因为不强制转换报错,内容是:
    E0144	"const char *" 类型的值不能用于初始化 "char *" 类型的实体
    网上的解决方法是将每一个元素强制转换
    */

    char* arr[4] = { (char *)"abcde",(char*)"fghij",(char*)"jlmno",(char*)"pqrst" };
    //获取数组大小;
    int n = sizeof(arr) / sizeof(arr[0]);
    cout << "显示每一个元素" << endl;
    for (int i = 0; i < n; i++)
    {
        //打印每一项的内容。arr[i]获取的是每一个元素的首地址
        cout << arr[i] << " ";
    }
    cout << endl;
    cout << "从每一个元素第三项开始显示" << endl;
    for (int i = 0; i < n; i++)
    {
        cout << arr[i] + 3 << " ";//这里是获取每个元素第三项的地址,显示的是第三项及以后的内容
    }
    cout << endl;
    cout << "显示第三项" << endl;
    for (int i = 0; i < n; i++)
    {
        cout << *(arr[i] + 3) << " ";
    }

}

在这里插入图片描述

3 指针的指针

num是个整型变量

*p接收num的地址

**p接收指针p的地址

//指针的指针;
void test03() {
    int num = 10;
    int* p = &num;
    int** q = &p;
    cout << "p的值:" << p << endl;;
    cout << "*p的值:" << *p << endl;
    cout << "q的值:" << q << endl;
    cout << "*q的值:" << *q << endl;
    cout << "**q的值:" << **q << endl;

}

在这里插入图片描述

4 数组地址和首元素地址

区分: 数组首元素地址 和 数组首地址
数组首元素地址:数组第一个元素的地址 &arr[0]==arr arr+1跳过的是第一个元素
数组首地址: 数组的地址 &arr &arr+1跳过整个数组
这两个数值上一样,但是意义不同

//数组地址和首元素地址;
void test04() {
    /*区分 数组首元素地址 和 数组首地址
    数组首元素地址:数组第一个元素的地址  &arr[0]==arr   arr+1跳过的是第一个元素
    数组首地址: 数组的地址 &arr   &arr+1跳过整个数组
    这两个数值上一样,但是意义不同
    */
    int arr[5] = { 12,34,56,78,91 };
    cout << "数组首元素地址:arr = " << arr << endl;
    cout << "跳过第一个元素:arr + 1 = " << arr + 1 << endl;
    cout << "数组首地址:&arr = " << &arr << endl;
    cout << "跳过整个数组 &arr + 1 = "<< &arr + 1 << endl;
}

在这里插入图片描述

5 数组指针

本质是:指针变量保存的是 数组的首地址

这里注意区分指针数组和数组指针
指针数组 int* p[5]
数组指针 int(* p)[5]

注意:对数组首地址取 * ==数组首元素地址

//数组指针;
void test05() {
    //本质是:指针变量保存的是 数组的首地址
    int arr[5] = { 10,20,30,40,50 };
    int(*p)[5] = NULL;//声明数组指针,小括号不可以去掉
    /*这里注意区分指针数组和数组指针
    指针数组 int*p[5]
    数组指针 int(*p)[5]
    */
    cout << "sizeof(p)=" << sizeof(p) << endl;//32位平台为4B
    cout << "p=" << p << endl;//因为初始化为空,所以大小为0
    //因为是数组指针,一个数组是5个元素,一个元素4B大小,所以一共20B,十六进制为14
    cout << "p+1=" << p + 1 << endl;

    //如何通过数组指针去访问数组元素
    /*
    思路:假设要拿到30这个元素。先获得第0个元素,+2就可以获得第2个元素

    注意:对数组首地址取 * ==数组首元素地址
    */
    int* q = arr;//获取首元素地址
    cout << "第二个元素为:" << *(q + 2) << endl;//q为首元素地址,q+2为第二个元素的地址,整体取 * 获取到具体的值
}

在这里插入图片描述

6 二维数组和数组指针

二维数组的组名代表的是第0行的行地址 +1跳过一个行
对行地址取 * 代表当前行第0列的列地址

//二维数组和数组指针;
void test06() {
    //声明二维数组
    int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
    /*
    二维数组的组名代表的是第0行的行地址  +1跳过一个行
    对行地址取 * 代表当前行第0列的列地址
    */
    cout << "arr=" << arr << endl;//第0行首地址
    cout << "arr + 1 =" << arr+1 << endl;//第一行首地址
    cout << "*arr=" << *arr << endl;//第0列首地址
    cout << "*arr+1=" << *arr+1 << endl;//第1列首地址
    cout << "**arr=" << **arr << endl;//第0行第0列的元素值
    cout << "*(*arr+1)=" << *(*arr+1) << endl;//第0行第1列元素的值
    cout << "**arr + 1 =" << **arr +1  << endl;//第0行第0列的元素值+1

}

在这里插入图片描述

三、指针与函数

如果想在函数内部 修改函数外部的值 需要将外部变量的地址 传递给函数

1 整型作为函数的参数

void setnum(int* p) //int * p=&num
{
	*p = 100;//*p==num
}


void test01() {
	int num = 0;
	setnum(&num);//单向传递--传地址
	cout << "num = " << num << endl;
}

传递的是地址

在这里插入图片描述

2 一维数组作为函数的参数

函数内部想要操作(读或写)外部数组元素,将数组名传递给函数

一维数组作为函数的形参 会被优化成指针变量

//遍历数组函数
void outputIntArray(int arr[5],int n) {
	cout << "内部sizeof(arr)= " << sizeof(arr) << endl;
}

//一维数组作为函数的参数
void test02() {
	int arr[5] = { 10,20,30,40,50 };
	int n = sizeof(arr) / sizeof(arr[0]);

	cout << "外部sizeof(arr)= " << sizeof(arr) << endl;
	//遍历数组
	outputIntArray(arr, n);
}

在这里插入图片描述

  • 不一样的原因:

    数组作为参数,会被编译器优化为指针变量。

    这里也就是优化成为 int * arr 也就是4B

//遍历数组函数
void outputIntArray(int arr[5],int n) {
	cout << "内部sizeof(arr)= " << sizeof(arr) << endl;
	for (int i = 0; i < n; i++)
	{
		//两种方式遍历,推荐第二种
		cout << *(arr + i) << " ";
		cout << arr[i] << " ";
	}
	cout << endl;

}

在这里插入图片描述

3 二维数组作为函数的参数

函数内部 想操作 函数外部的二维数组,需要将二维数组组名传递给函数

二维数组作为函数的形参 会被优化成一维的数组指针

void outputIntDoubleArray(int arr[3][4], int row, int col) {
	cout << "内部sizeof(arr)= " << sizeof(arr) << endl;
}
void test03(){

	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	//求行数
	int row = sizeof(arr) / sizeof(arr[0]);
	//求列数
	int col = sizeof(arr[0]) / sizeof(arr[0][0]);
	cout << "外部sizeof(arr)= " << sizeof(arr) << endl;
	outputIntDoubleArray(arr, row, col);
}

在这里插入图片描述

  • 这里的数组作为参数也被优化为指针变量,但是优化为数组指针 -> int (*arr) [4]

    但是本质还是指针,所以大小还是4B(32位平台)

//遍历二维数组
void outputIntDoubleArray(int arr[3][4], int row, int col) {
	cout << "内部sizeof(arr)= " << sizeof(arr) << endl;
	int i = 0, j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			cout << arr[i][j] << " ";
		}
		cout << endl;
	}
}

在这里插入图片描述

4 函数的返回值类型为指针类型

将函数内部的合法地址 通过返回值 返回给函数外部使用。

int* getAddr() {
	int data = 100;
	return &data;
}

void test04() {
	int* p = NULL;
	p = getAddr();
	cout << "*p = " << *p << endl;
}

这里可以正常运行,但是有一个错误。就是不要返回局部变量的地址,也就是不要返回getAddr函数里的data变量的地址。虽然本次可以正常运行,但是变量多了就会混乱,所以在函数里使用static。

static修饰后是静态的局部变量,他的生命周期是整个进程,也就是说返回这个地址后,函数调用完成,他的空间不会被释放,空间存在,P就指向的是合法空间

int* getAddr() {
	static int data = 100;
	return &data;
}

void test04() {
	int* p = NULL;
	p = getAddr();
	cout << "*p = " << *p << endl;
}

在这里插入图片描述

5 函数指针

函数名 代表函数的入口地址

函数指针 : 本质是一个指针变量,只是该变量 保存的是函数的入口地址

int myAdd(int x, int y) {

	return x + y;
}

void test05() {
	int (*p)(int x, int y) = NULL;//*p的小括号不可以去掉,要不然就变成:函数的声明,返回值是int指针
	cout << "sizeof(p) = " << sizeof(p) << endl;
	
	//函数指针 和 函数入口地址 建立关系
	p = myAdd;//函数名代表函数入口地址。不要加小括号,要不然成了函数调用。p是int * ,返回值是int,报错。
	
			  //通过p调用myAdd函数  (函数+(实参))
	cout << p(10, 20) << endl;
}

在这里插入图片描述

5.1 函数指针变量注意
  1. 函数指针变量 不要+1 无意义

  2. 不要对函数指针变量取* 无意义

  3. 调用的时候,加上 * 也会被优化。*p会被优化为p

    void test05() {
    	int (*p)(int x, int y) = NULL;//*p的小括号不可以去掉,要不然就变成:函数的声明,返回值是int指针
    	cout << "sizeof(p) = " << sizeof(p) << endl;
    	//函数指针 和 函数入口地址 建立关系
    	p = myAdd;//函数名代表函数入口地址。不要加小括号,要不然成了函数调用。p是int * ,返回值是int,报错。
    	cout << (*****p)(10, 20) << endl;//(*****p)会被优化为p
    }
    
  4. 函数指针变量 判断大小 > < 无意义

  5. 函数指针变量 可以赋值 p2=p1

    其实就是将两个函数指针变量指向同一个函数入口地址

  6. 函数指针变量 可以判断相等 p2==p1

    其实就是判断是否指向同一个函数入口地址

5.2 函数指针变量 使用typedef定义
void test05_1() {
	//使用typedef给函数指针类型取别名
	//以下两个等价
	//1
	typedef int (*FUN_TYPE)(int x, int y);
	FUN_TYPE p = myAdd;
	//2
	int (*p)(int x, int y) = NULL;
	cout << "sizeof(p) = " << sizeof(p) << endl;
	p = myAdd;

	cout << p(10, 20) << endl;
}
5.3 函数指针作为函数的参数
  • 让算法功能多样化

案例1 设计一个算法,实现加减乘除

//案例1 算法设计
int myAdd(int x, int y) {
	return x + y;
}
int mySub(int x, int y) {
	return x - y;
}
int myMul(int x, int y) {
	return x * y;
}
int myDiv(int x, int y) {
	return x / y;
}
//设计算法,操作上面的函数
int myCalc(int x, int y, int(*func)(int, int)) {
	return func(x, y);
}
void test06() {
	cout << myCalc(10, 20, myAdd) << endl;
	cout << myCalc(10, 20, mySub) << endl;
	cout << myCalc(10, 20, myMul) << endl;
	cout << myCalc(10, 20, myDiv) << endl;

}

在这里插入图片描述

四、 动态内存

按照需要动态分配内存空间,解决空间分配问题

也可把不再需要的空间回收再次利用

new 负责申请堆栈空间

delete 负责释放空间

new 和delete 要成对存在,要不然进程不消失,空间就一直存在,造成空间泄露,别人无法操作该空间。

1 基本操作

void test01() {
	int* p = NULL;
	p = new int;//从堆区申请int类型大小的空间
	*p = 100;
	cout << "*p = " << *p << endl;
	cout << "p = " << p << endl;
	//释放空间
	delete p;

	//申请空间的同时,初始化空间内容
	int* p1 = NULL;
	p1 = new int(100);
	cout << "*p1 = " << *p1 << endl;
	cout << "p1 = " << p1 << endl;

	delete p1;
}

在这里插入图片描述

2 操作数组空间

如果new 有[],那么delete也要有[]

void test02() {
	int* arr = NULL;
	arr = new int[5]{ 10,20,30,40,50 };
	cout << "输出地址:" << endl;

	for (int i = 0; i < 5; i++)
	{
		cout << arr + i << " ";
	}
	cout << endl;
	cout << "指针方式输出内容:" << endl;

	for (int i = 0; i < 5; i++)
	{
		cout << *(arr + i) << " ";
	}
	cout << endl;

	cout << "数组方式输出内容:" << endl;

	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << " ";
	}
	//如果new 有[],那么delete也要有[]
	delete []arr;
}

在这里插入图片描述

五、 字符串处理函数

以str开头为字符串处理函数 默认遇到“\0”结束操作

都要使用头文件 #include<string.h>

1 测量字符串的长度 strlen

传入的参数是 需要测量的字符串的首元素地址

#include<string.h>

void test01() {
	char str1[128] = "HELLO";
	char str2[128] = "HE\0LLO";

	cout << "strlen(str1)= " << strlen(str1) << endl;
	cout << "strlen(str2)= " << strlen(str2);

}

在这里插入图片描述

2 字符串拷贝函数 strcpy

注意:拷到的新的空间,要有足够的合法地址才行

有两个函数 strcpy strncpy

一个是遇到\0结束

另一个会有一个参数限制拷贝多少,但是这个参数和\0同时遇到的话,先遇到谁谁就起作用

例如:限制拷贝10个,第二个位置上有\0,那么就拷贝2个

​ 限制拷贝10个,第100个位置上有\0,那么就拷贝10个

这里遇到一个编译器的问题。编译器为VS 2019

提示错误信息为:

警告	C4996	'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.	字符串处理函数	F:\数据结构\字符串处理函数\字符串处理函数.cpp	20

​ 微软认为有些方法不安全,不让使用。

解决方法:

  1. 在最头部添加宏定义: #define _CRT_SECURE_NO_DEPRECATE
  2. 在方法后面加_s ,例如strcpy_s
  3. 按照下图更改配置。

本次使用的是第三种解决方法

在这里插入图片描述

void test02() {
	//设置接收额数组
	char dst[128] = "";
	//被拷贝的数组
	char src[] = "hello\0world";
	strcpy(dst, src);
	cout << dst << endl;

	char dst1[] = "";
	char src1[] = "hello world";
	strcpy(dst1, src1);
	cout << dst1 << endl;
}

在这里插入图片描述

虽然这里可以正常输出,但是有一个严重错误,就是dst1[]数组没有写大小,

dst1数组开始是“\0”,只有1字节 当src1拷贝到dst1中 造成溢出(内存污染)

3 字符串追加 strcat

将一个字符串拼接到一个字符串的尾部

void test03() {
	//被追加的字符串空间一定足够大
	char dst[128] = "hello ";
	char src[] = "world";
	strcat(dst, src);
	cout << dst << endl;
}

在这里插入图片描述

4 字符串比较 strcmp

含义
>0s1字符串 > s2字符串
<0s1字符串 < s2字符串
==0s1字符串 = s2字符串

比较是一个一个比较,相等的话转到下一个,比较阿斯克码

void test04() {
	char str1[128] = "";
	char str2[128] = "";
	cout << "请输入第一个字符串:";
	cin >> str1;
	cout << "请输入第二个字符串:";
	cin >> str2;
	if (strcmp(str1,str2)>0)
	{
		cout << str1 << " 大于 " << str2 << endl;
	}
	else if (strcmp(str1, str2) < 0)
	{
		cout << str1 << " 小于 " << str2 << endl;
	}
	else if (strcmp(str1, str2) == 0)
	{
		cout << str1 << " 等于 " << str2 << endl;
	}
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

六、 结构体

1 结构体

1.1 结构体概述

为了将不同类型的数据组合成一个整体

struct关键字 可以将不同类型封装在一起,行程新的结构就叫“结构体”

定义结构体类型时,系统不会为结构体类型开辟空间,只会为结构体类型定义的变量开辟空间

定义结构体类型时,不要给成员初始化值

结构体成员拥有独立的空间

访问结构体变量中成员的方法:结构体变量.成员名

struct Student
{
    int num;//结构体成员
    char name[32];
};
//结构体定义变量
Student lucy;//lucy 为结构体变量名
1.2 结构体变量初始化和清空
void *memset(void *_Dst,int _Val,size_t _Size);

使用memset 将地址为 _Dst 开始,长度为 _Size 的所有字节赋值为 _Val

如果没有初始化,成员内容不确定

#include<string.h>


struct Student
{
	int num;
	char name[32];
};

void test01() {
	Student lucy = {12,"lucy"};
	lucy.num = 10;
	cout << "num = " << lucy.num << endl;
	cout << "name = " << lucy.name << endl;
//清空整个结构体变量
	memset(&lucy, 0, sizeof(lucy));
	cout << "num = " << lucy.num << endl;
	cout << "name = " << lucy.name << endl;
}

在这里插入图片描述

1.3 使用键盘赋值
#include<string.h>

void test02() {
	Student lucy;
	memset(&lucy, 0, sizeof(lucy));

	cout << "请输入学号 姓名:" << endl;
	cin >> lucy.num >> lucy.name;

	cout << "num = " << lucy.num << endl;
	cout << "name = " << lucy.name << endl;

}

在这里插入图片描述

1.4 单独操作结构体中的单元

注意:当变量的成员有数组的时候,数组名为符号常量,不允许用=给该数组赋值(数组名代表首元素地址),可以使用strcpy赋值

lucy.name = "bob";

这种写法是错的

void test03() {
	Student lucy = { 100,"lucy" };

	lucy.num += 100;

	strcpy(lucy.name, "Bob");

	cout << "num = " << lucy.num << endl;
	cout << "name = " << lucy.name << endl;
}

在这里插入图片描述

1.5 相同类型结构体变量之间赋值方法
void test04() {
	Student lucy = { 100,"lucy" };
	Student Bob;
#if 1
	//第一种方法:逐个成员赋值(组训成员类型)注意数组不能直接=
	Bob.num = lucy.num;
	strcpy(Bob.name, lucy.name);
#elif 0
	//第二种方法:相同类型的结构体变量  可以直接赋值(推荐)
	Bob = lucy;
#else
	//第三种方法:内存拷贝(第二种方法的底层实现)
	memcpy(&Bob, &lucy, sizeof(Student));
#endif // 1
	cout << Bob.name << " " << Bob.num << endl;
}

在这里插入图片描述

2 结构体嵌套结构体

注意访问到最底层,分析清楚每一个成员属于哪一个层次

#include<string.h>
#include <iostream>
using namespace std;


struct Data
{
	int year;
	int month;
	int day;
};

struct Student
{
	int num;
	char name[32];
	Data ob;
};
int main()
{
	Student lucy = { 100,"lucy",{2022,7,4} };
	cout << lucy.num << " " << lucy.name << endl;
	cout << lucy.ob.year << " " << lucy.ob.month << " " << lucy.ob.day << endl;
}

在这里插入图片描述

3 结构体数组

本质数数组,只是每一个元素是结构体

//结构体数组练习
void test05() {
	Student arr[5] = { {100,"lucy"},{100,"bob"}, {100,"tom"}, {100,"jack"}, {100,"jerry"} };
	//求数组元素个数
	int n = sizeof(arr) / sizeof(arr[0]);

	for (int i = 0; i < n; i++)
	{
		//数组的每一个元素是一个结构体变量
		cout << arr[i].name << " " << arr[i].num << endl;
	}
}

在这里插入图片描述

4 结构体指针变量

本质是 变量 只是该变量保存的是结构体变量的地址

如果是地址可以直接使用-> 访问成员,如果是结构体变量,需要使.访问成员

// 结构体指针变量

void test06() {
	Student lucy = { 100,"lucy" };
	//定义指针
	Student* p = NULL;
	p = &lucy;
	cout << lucy.num << " " << lucy.name << endl;
	cout << (*p).num << " " << (*p).name << endl;
	//通过指针变量 使用-> 访问成员
	cout << p->num << " " << p->name << endl;
	//如果是地址可以直接使用-> 访问成员,如果是结构体变量,需要使.访问成员

}

在这里插入图片描述

4.1 结构体数组元素的指针变量

指针变量 保存结构体数组 元素的地址

下面例子中:

一维数组当做参数传递。一维数组当函数参数会被优化为数组首元素地址

参考[一维数组作为函数的参数](# 2 一维数组作为函数的参数)

所以构建函数的时候有两种表达方式

  • void inputStuArray(Student *arr,int n){}
  • void inputStuArray(Student arr[n],int n){}
void inputStuArray(Student *arr,int n) {
	cout << "请输入" << n << "个学生的信息(num,name)" << endl;
	int i = 0;
	for ( i = 0; i < n; i++)
	{
		cin >> (arr + i)->num >> (arr + i)->name;
	}
}

void sortStuArray(Student *arr,int n) {
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n-i-1; j++)
		{
			if ((arr + j)->num > (arr + j + 1)->num) {//这里也可以是 arr[j].num
				Student temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

void outputStuArrary(Student arr[5], int n) {
	for (int  i = 0; i < n; i++)
	{
		cout << arr[i].name << "的成绩是:" << arr[i].num << endl;//这里可以使用 arr->name
		
	}
}

void  test07()
{
	Student arr[5];
	memset(arr, 0, sizeof(arr));
	int n = sizeof(arr) / sizeof(arr[0]);

	//封装函数获取键盘输入
	inputStuArray(arr, n);

	//对结构体数组按学号排序
	sortStuArray(arr,n);

	//输出结构体数组元素的内容
	outputStuArrary(arr, n);
}

在这里插入图片描述

5 结构体的指针成员

struct Stu
{
  int num;
  char * name;
};
Stu lucy = {100,"Hello World"};

lucy.name 保存的是"Hello World"的首元素地址,而"Hello World"字符串本身存储在文字常量区

5.1 结构体指针指向堆区
#include <iostream>
#include<string.h>
using namespace std;

struct Stu
{
	int num;
	char* name;
};

void test01() {
	Stu lucy;
	lucy.num = 100;
	lucy.name = new char[32];
	strcpy(lucy.name, "hello world");
	
	cout << lucy.num << " " << lucy.name << endl;
	delete[] lucy.name;
}
5.2 浅拷贝

相同类型的结构体变量可以整体赋值,默认赋值方式为:浅拷贝

浅拷贝:将结构体变量空间内容复制一份到另一个相同类型的结构体变量空间中

如果结构体中没有指针成员 浅拷贝 不会带来问题

如果结构体中指针成员 浅拷贝 会带来多次释放堆区空间的问题

Stu lucy={100,"hello world"};
Stu bob;
bob=lucy;

在这里插入图片描述

造成多次释放堆区空间的问题就是,释放过一次lucy.name后,再次释放bob.name的时候就又一次释放了该地址。

5.3 深拷贝

解决浅拷贝的问题 使用 深拷贝

深拷贝:为指针成员申请独立的空间,然后再拷贝内容

//深拷贝演示
void test02() {
	//构建lucy
	Stu lucy;
	lucy.num = 100;
	lucy.name = new char[32];
	strcpy(lucy.name, "hello world");
	//构建bob
	Stu bob;
	bob.num = lucy.num;
	bob.name = new char[32];//申请一个新的地址空间
	strcpy(bob.name, lucy.name);//复制内容到新空间

	//打印内容
	cout << bob.num << " " << bob.name << endl;
	//释放两个空间
	delete[] lucy.name;
	delete[] bob.name;

}

在这里插入图片描述

5.4 结构体变量在堆区 结构体的指针成员也指向堆区

注意:释放的时候先释放指针成员的空间,再释放结构体指针。否则找不到指针成员的地址,无法访问到指针成员

//结构体指针在堆区,结构体中的指针成员也在堆区
void test03() {
	//结构体在堆区
	Stu* p = NULL;
	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;
}

6 结构体的对齐规则

自动对齐规则

  1. 确定分配单位(一行分配多少个字节)

    结构体中最大的基本类型 长度决定

  2. 确定成员的偏移量‘

    成员偏移量 = 成员自身类型的整数倍

  3. 收尾工作

    结构体的总大小 = 分配单位整数倍

七、 链表

1 概述

不需要事先知道数据的个数,在使用中动态申请,插入删除不需要移动数据

缺点:遍历效率低

2 静态链表

阅读下面代码的注释,注意事项已标明

#include <iostream>
#include<string.h>
using namespace std;

//设计节点,本质是结构体
struct stu
{
	//数据域
	int num;
	char name[32];

	//指针域
	struct stu* next;

};

int main()
{
	//初始化五个节点,指针域都为空
	struct stu node1 = { 100,"lucy",NULL };
	struct stu node2 = { 200,"bob",NULL };
	struct stu node3 = { 300,"tom",NULL };
	struct stu node4 = { 400,"jerry",NULL };
	struct stu node5 = { 500,"jack",NULL };
	//定义链表头
	struct stu* head = &node1;//head指针变量保存第一个节点的地址

	node1.next = &node2;
	node2.next = &node3;
	node3.next = &node4;
	node4.next = &node5;

	//遍历
	//静态链表可以有名字
	struct stu* pb = head;
	while (pb!=NULL)
	{
		//访问数据
		cout << pb->name << " " << pb->num << endl;

		//pb移动到下一个节点的位置
		//注意:这里不能直接让pb++,链表的操作不是++,让pb移动就是把pb需要移动到的地址告诉pb。
		pb = pb->next;
	}

}


在这里插入图片描述

3 动态链表

动态链表的学习以学生管理系统为例,进行学习

3.1 main函数设计

主要构建设计有什么功能

#include <iostream>
#include<string.h>
using namespace std;


//帮助信息
void help() {
	cout << "********************************" << endl;
	cout << "* help:帮助信息                *" << endl;
	cout << "* insert:插入链表节点          *" << endl;
	cout << "* print:遍历链表               *" << endl;
	cout << "* search:查询链表某个节点      *" << endl;
	cout << "* delete:删除链表某个节点      *" << endl;
	cout << "* free:释放整个链表            *" << endl;
	cout << "* quit:退出程序                *" << endl;
	cout << "* cls :清空屏幕                *" << endl;
	cout << "********************************" << endl;
}

int main()
{
	help();
	//在while循环里执行操作
	while (1)
	{
		char cmd[64] = "";
		cout << "请输入操作指令:";
		cin >> cmd;
		if (strcmp(cmd, "help") == 0)
		{
			help();
		}
		else if (strcmp(cmd, "insert") == 0)
		{
			cout << "-------insert------" << endl;
		}
		else if (strcmp(cmd, "search") == 0)
		{
			cout << "-------search------" << endl;
		}
		else if (strcmp(cmd, "print") == 0)
		{
			cout << "-------print------" << endl;
		}
		else if (strcmp(cmd, "delete") == 0)
		{
			cout << "-------delete------" << endl;
		}
		else if (strcmp(cmd, "free") == 0)
		{
			cout << "-------free------" << endl;
		}
		else if (strcmp(cmd, "quit") == 0)
		{
			return 0;
		}
		else if (strcmp(cmd, "cls") == 0)
		{
			system("cls");
		}
		else
		{
			cout << "请输入正确的指令" << endl;
		}
	}
}
3.2 链表插入节点

定义一个链表的节点类型

//定义链表节点类型
struct Stu
{
	//数据域
	int num;
	char name[32];
	//指针域
	Stu* next;
};

插入的话需要知道往哪里插入,所以构建一个链表头

//构建一个链表头
Stu* head = NULL;

完善插入选择

		else if (strcmp(cmd, "insert") == 0)
		{
			cout << "请输入要插入的节点信息(num name):" << endl;
			Stu tmp;
			cin >> tmp.num >> tmp.name;
			head = insertLink(head, tmp);
		}

完善插入函数

自己犯错的地方:

判断完链表存在后,不知道怎么操作,误以为head也存储了内容。其实head只是存储了第一个节点的地址,head就等于第一个节点

//插入函数 使用头插法
Stu* insertLink(Stu* head, Stu tmp) {
	//从堆区申请带插入的节点空间
	Stu* pi = new Stu;
	//给空间赋值
	*pi = tmp;
	pi->next = NULL;

	//判断链表是否存在
	if (NULL==head)//不存在
	{
		head = pi;
	}
	else//存在
	{
		pi->next = head;
		head = pi;
	}
	return head;
}

尾插法

大部分和头插法一样,

但是判断链表存在后,需要寻找尾节点。这个时候保持头节点不动,用一个新的节点来依次遍历,如果该节点的next为NULL,证明该节点为尾节点。

//插入函数 尾插法(未使用)
Stu* insertLink1(Stu* head, Stu tmp) {
	//从堆区申请带插入的节点空间
	Stu* pi = new Stu;
	//给空间赋值
	*pi = tmp;
	pi->next = NULL;

	//判断链表是否存在
	if (NULL == head)//不存在
	{
		head = pi;
	}
	else//存在
	{
		Stu* pb = head;
		//寻找尾节点
		while (pb->next!=NULL)
		{
			pb = pb->next;
		}
		//在尾节点插入pi
		pb -> next = pi;
	}
	return head;
}
3.3 遍历链表

判断链表存在后,遍历的时候要用一个新的指针去遍历,head不能动。

//遍历链表
void printLink(Stu* head) {
	//判断链表是否存在
	if (NULL==head)
	{
		cout << "链表不存在" << endl;
		return;
	}
	else
	{
		Stu* pb = head;
		while (pb ! = NULL)
		{
			cout << pb->name << " " << pb->num << endl;
			pb = pb->next;
		}
		return;
	}
}
3.4 按姓名查询
//查询链表
Stu* searchLink(Stu* head, char* name) {
	//判断链表是否存在
	if (NULL==head)
	{
		cout << "链表不存在" << endl;
		return NULL;
	}
	else
	{
		//逐个节点查询
		Stu* pb = head;
		while ((strcmp(pb->name,name)!=0)&&(pb->next!=NULL))//判断pb的名字和要查询的名字不一致且还有下一个节点的时候,循环继续
		{
			pb = pb->next;

		}
		/*此时跳出了循环,有两种情况。
		1 查找到要寻找的名字了
		2 链表遍历完了
		*/
		if (strcmp(pb->name,name)==0)
		{
			return pb;
		}
		else
		{
			return NULL;
		}
		return NULL;
	}
}
3.5 删除节点

删除节点的时候注意,要记得上一个节点,这样才能删除掉当前的节点(让上一个节点的next指向当前节点的next)

还要注意删除的时候删除的是头节点还是链表的中尾部节点

为什么中尾部和在一起

在下面的代码中pf记录上一个节点,pb是当前节点。删除尾部节点和中间节点的时候,代码都可以使用pf->next=pb->next。因为删除尾部节点的时候就是让pf的next为NULL,而pb的next恰好是NULL。所以这俩可以和在一起。

//删除节点
Stu* deleteLink(Stu* head, int num) {
	//判断链表是否存在
	if (NULL == head)
	{
		cout << "链表不存在" << endl;
		return NULL;
	}
	else
	{
		//逐个节点比较
		Stu* pf = head, * pb = head;
		while ((pb->num != num) && (pb->next != NULL)) {
			pf = pb;
			pb = pb->next;
		}
		//循环结束
		if (pb->num==num)//找到删除点
		{
			if (pb==head)//头节点删除
			{
				head = head->next;
				delete pb;
			}
			else//删除中尾部节点
			{
				pf->next = pb->next;
				delete pb;
			}
		}
		else
		{
			cout << "未找到要删除的节点" << endl;
		}
		return head;
	}
}
3.6 释放链表

释放是逐个节点进行释放,要注意pb和head的关系

//释放链表
Stu* freeLink(Stu* head) {
	
	//判断链表是否存在
	if (NULL == head)
	{
		cout << "链表不存在" << endl;
		return NULL;
	}
	else//逐个节点释放
	{
		Stu* pb = head;
		while (pb != NULL) {
			head = head->next;
			delete pb;
			pb = head;
		}
		return head;

	}
}
3.7 整体的代码
#include <iostream>
#include<string.h>
using namespace std;


//定义链表节点类型
struct Stu
{
	//数据域
	int num;
	char name[32];
	//指针域
	Stu* next;
};
//构建一个链表头
Stu* head = NULL;

//帮助信息
void help() {
	cout << "********************************" << endl;
	cout << "* help:帮助信息                *" << endl;
	cout << "* insert:插入链表节点          *" << endl;
	cout << "* print:遍历链表               *" << endl;
	cout << "* search:查询链表某个节点      *" << endl;
	cout << "* delete:删除链表某个节点      *" << endl;
	cout << "* free:释放整个链表            *" << endl;
	cout << "* quit:退出程序                *" << endl;
	cout << "* cls :清空屏幕                *" << endl;
	cout << "********************************" << endl;
}

//插入函数 使用头插法
Stu* insertLink(Stu* head, Stu tmp) {
	//从堆区申请带插入的节点空间
	Stu* pi = new Stu;
	//给空间赋值
	*pi = tmp;
	pi->next = NULL;

	//判断链表是否存在
	if (NULL==head)//不存在
	{
		head = pi;
	}
	else//存在
	{
		pi->next = head;
		head = pi;
	}
	return head;
}
//插入函数 尾插法(未使用)
Stu* insertLink1(Stu* head, Stu tmp) {
	//从堆区申请带插入的节点空间
	Stu* pi = new Stu;
	//给空间赋值
	*pi = tmp;
	pi->next = NULL;

	//判断链表是否存在
	if (NULL == head)//不存在
	{
		head = pi;
	}
	else//存在
	{
		Stu* pb = head;
		//寻找尾节点
		while (pb->next!=NULL)
		{
			pb = pb->next;
		}
		//在尾节点插入pi
		pb -> next = pi;
	}
	return head;
}

//遍历链表
void printLink(Stu* head) {
	//判断链表是否存在
	if (NULL==head)
	{
		cout << "链表不存在" << endl;
		return;
	}
	else
	{
		Stu* pb = head;
		while (pb!=NULL)
		{
			cout << pb->name << " " << pb->num << endl;
			pb = pb->next;
		}
		return;
	}
}

//查询链表
Stu* searchLink(Stu* head, char* name) {
	//判断链表是否存在
	if (NULL==head)
	{
		cout << "链表不存在" << endl;
		return NULL;
	}
	else
	{
		//逐个节点查询
		Stu* pb = head;
		while ((strcmp(pb->name,name)!=0)&&(pb->next!=NULL))//判断pb的名字和要查询的名字不一致且还有下一个节点的时候,循环继续
		{
			pb = pb->next;

		}
		/*此时跳出了循环,有两种情况。
		1 查找到要寻找的名字了
		2 链表遍历完了
		*/
		if (strcmp(pb->name,name)==0)
		{
			return pb;
		}
		else
		{
			return NULL;
		}
		return NULL;
	}
}


//删除节点
Stu* deleteLink(Stu* head, int num) {
	//判断链表是否存在
	if (NULL == head)
	{
		cout << "链表不存在" << endl;
		return NULL;
	}
	else
	{
		//逐个节点比较
		Stu* pf = head, * pb = head;
		while ((pb->num != num) && (pb->next != NULL)) {
			pf = pb;
			pb = pb->next;
		}
		//循环结束
		if (pb->num==num)//找到删除点
		{
			if (pb==head)//头节点删除
			{
				head = head->next;
				delete pb;
			}
			else//删除中尾部节点
			{
				pf->next = pb->next;
				delete pb;
			}
		}
		else
		{
			cout << "未找到要删除的节点" << endl;
		}
		return head;
	}
}

//释放链表
Stu* freeLink(Stu* head) {
	
	//判断链表是否存在
	if (NULL == head)
	{
		cout << "链表不存在" << endl;
		return NULL;
	}
	else//逐个节点释放
	{
		Stu* pb = head;
		while (pb != NULL) {
			head = head->next;
			delete pb;
			pb = head;
		}
		return head;

	}
}


int main()
{
	help();
	//在while循环里执行操作
	while (1)
	{
		char cmd[64] = "";
		cout << "请输入操作指令:";
		cin >> cmd;
		if (strcmp(cmd, "help") == 0)
		{
			help();
		}
		else if (strcmp(cmd, "insert") == 0)
		{
			cout << "请输入要插入的节点信息(num name):" << endl;
			Stu tmp;
			cin >> tmp.num >> tmp.name;
			head = insertLink(head, tmp);
		}
		else if (strcmp(cmd, "search") == 0)
		{
			cout << "请输入要查询的姓名:" << endl;
			char name[32] = "";
			cin >> name;
			Stu* ret = NULL;
			ret = searchLink(head, name);
			if (ret!=NULL)
			{
				cout << "查询的结果: num= " << ret->num << " name= " << ret->name << endl;
			}
			else
			{
				cout << "查无此人" << endl;
			}
		}
		else if (strcmp(cmd, "print") == 0)
		{
			printLink(head);
		}
		else if (strcmp(cmd, "delete") == 0)
		{
			cout << "请输入要删除的学号:" << endl;
			int num = 0;
			cin >> num;
			head = deleteLink(head, num);//如果删除的是头节点,头节点就需要更新,所以拿头节点接一下
		}
		else if (strcmp(cmd, "free") == 0)
		{
			head = freeLink(head);
			cout << "释放完成" << endl;
		}
		else if (strcmp(cmd, "quit") == 0)
		{
			return 0;
		}
		else if (strcmp(cmd, "cls") == 0)
		{
			system("cls");
		}
		else
		{
			cout << "请输入正确的指令" << endl;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不会挂科i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值