目录
使用C++手动封装一个顺序表,包含成员数组一个,成员变量N个
思维导图:
学习内容:
1. 引用(reference)
1.1 引用的引入
1> 在C语言中,进行数据传递的方式有两种,分别是值传递和地址传递,对于数据的传递都需要在被调函数中设置一个载体,对主调函数中的数据进行间接访问
2> 在C++中引入的引用的概念,就没有值传递和地址传递的区分了,直接传递的就是主调函数中的实参本身,并且被调函数中不需要申请载体的空间,直接对实参的值进行操作
3> 引用相当于给内存空间起个别名。
1.2 引用的定义格式
类型名 &引用名 = 目标名;
例如:int num = 520;
int &ref = num; //定义一个引用,并指向一个num目标
对&又进一步使用,&的使用方式
1、&表示单目运算符,取地址运算,后面紧跟一个变量
2、&&表示双目运算符逻辑与运算
3、&表示双目运算符,按位与运算
4、&表示定义引用
1.3 引用的注意事项
1、引用的使用跟普通变量的使用一样,跟引用的目标一样正常使用
2、引用在定义时,必须用目标对其进行初始化,否则会报错
3、引用和目标的类型必须保持一致(也可以不一致,后期继承和多态时讲父类指针或引用可以指向子类对象时)
4、引用一旦定义并执行目标后,后期就不能再更改目标了
5、一个目标可以定义多个引用,多个引用与目标都是同一个东西
#include <iostream>
using namespace std;
int main()
{
int num = 520; //在内存中申请4个字节,存储数据为520
int &ref = num; //此时给num定义一个引用,后期这两个都是同一个东西
//int &r; //定义引用不初始化会直接报错
//string &re = num; //定义引用时,类型必须与目标保持一致
cout<<"num = "<<num<<" ref = "<<ref<<endl; //值相同
cout<<"&num = "<<&num<<" &ref = "<<&ref<<endl; //地址相同
num = 1314; //对num进行改变
cout<<"num = "<<num<<" ref = "<<ref<<endl; //值相同
ref = 666;
cout<<"num = "<<num<<" ref = "<<ref<<endl; //值相同
int &ref2 = ref; //给引用定义一个引用
int &ref3 = num; //给一个目标定义多个引用
cout<<"num = "<<num<<" ref = "<<ref<<" ref2 = "<<ref2<<" ref3 = "<<ref3<<endl; //值相同
cout<<"&num = "<<&num<<" &ref = "<<&ref<<" &ref2 = "<<&ref2<<" &ref3 = "<<&ref3<<endl; //值相同
cout<<"sizeof(num) = "<<sizeof(num) <<" sizeof(ref) = "<<sizeof(ref)<<endl; //大小相同
//验证引用的目标一旦指定,就不能再更改
int key = 12345;
//ref = key; //? 该语句是将key的值赋值给ref也就是赋值给num,并不是将ref重新指向key
//&ref = key; //? 报错,&ref是取得ref的地址,不能对地址常量赋值
//int ref = key; //? 报错 ref引用重复定义
return 0;
}
1.4 常引用 const
1> 对于变量而言,可以是普通变量,也可以是常变量
2> 对应的引用也可以是普通引用和常引用
3> 有四种引用与目标的搭配
1、普通引用 普通变量
2、普通引用 常变量
3、常引用 普通变量
4、常引用 常变量
#include <iostream>
using namespace std;
int main()
{
//普通引用目标为普通变量没有问题
int num = 520; //普通变量 对数据可读可写
int &ref1 = num; //普通引用 对数据可读可写
/******************************************/
//由于目标本身具有常属性,而引用是一个变量,所以报错,不能将普通引用目标为常变量
const int key = 1314; //常变量 对数据可读不可写
//int &ref2 = key; //普通引用
/******************************************/
//常引用的目标可以是普通变量
int value = 666; //普通变量 变量自身对数据可读可写
const int &ref3 = value; //常引用 引用对数据的处理可读不可写
cout<<"ref3 = "<<ref3<<endl; //可读
//ref3 = 999; //不可写
/******************************************/
//常引用可以引用的目标为常变量
const int number = 999; //常变量 对数据可读不可写
const int &ref4 = number; //常引用 对数据可读不可写
return 0;
}
1.5 引用与指针的关系
指针变量也是一个变量,可以定义一个指针变量的引用,但是一般不对指针变量定义引用
#include <iostream>
using namespace std;
int main()
{
int num = 520; //普通变量
int *ptr = # //定义指针变量指向普通变量
int * &ref = ptr; //定义了一个指针变量的引用
cout<<"num = "<<num<<endl; //使用值
cout<<"*ptr = "<<*ptr<<endl; //使用指针变量
cout<<"*ref = "<<*ref<<endl; //使用指针的引用
return 0;
}
1.6 引用作为函数的参数
1> 引用作为函数的参数,传递的是实参本身,没有值传递和地址传递之说
#include <iostream>
using namespace std;
//定义交换函数1,完成值传递
void swap1(int num, int key)
{
int temp = num;
num = key;
key = temp;
cout<<"swap1:: num = "<<num<<" key = "<<key<<endl;
}
//定义交换函数2,接受地址进行操作
void swap2(int *p, int *q)
{
int *t = p;
p = q;
q = t;
cout<<"swap1:: *p = "<<*p<<" *q = "<<*q<<endl;
}
//定义交换函数3,接受地址进行操作
void swap3(int *p, int *q)
{
int t = *p;
*p = *q;
*q = t;
cout<<"swap1:: *p = "<<*p<<" *q = "<<*q<<endl;
}
//定义交换函数4,完成地址传递,形参使用引用接受
void swap4(int &num, int &key)
{
int temp = num;
num = key;
key = temp;
cout<<"swap1:: num = "<<num<<" key = "<<key<<endl;
}
int main()
{
int num = 520;
int key = 1314;
//调用交换函数1
swap1(num, key); //1314 520
cout<<"main:: num = "<<num<<" key = "<< key<<endl; //520 1314
cout<<"**************************************************"<<endl;
//调用交换函数2
swap2(&num, &key); //1314 520
cout<<"main:: num = "<<num<<" key = "<< key<<endl; //520 1314
cout<<"**************************************************"<<endl;
//调用交换函数3
swap3(&num, &key); //1314 520
cout<<"main:: num = "<<num<<" key = "<< key<<endl; //1314 520
cout<<"**************************************************"<<endl;
//调用交换函数4
swap4(num, key); //520 1314
cout<<"main:: num = "<<num<<" key = "<< key<<endl; //520 1314
cout<<"**************************************************"<<endl;
return 0;
}
1.7 引用作为函数的返回值
1> 引用作为函数的返回值,返回的是一个左值
2> 跟指针作为函数的返回值一样,必须返回一个生命周期比较长的变量
3> 能够返回的类型
1、一定不能返回局部变量
2、全局变量
3、静态局部变量
4、堆区申请的空间中的值
#include <iostream>
using namespace std;
//引用作为函数的返回值,返回的是一个左值
int &fun()
{
//int num = 520;
//return num; //返回局部变量的空间是不合法的
static int key = 520;
return key; //返回生命周期比较长的数据
}
int main()
{
fun() = 1314; //引用函数作为左值使用
cout<<"fun() = "<<fun()<<endl; //1314
int &ref = fun(); //相当于给函数中的key又在主函数中起个别名
ref = 666;
cout<<"fun() = "<<fun()<<endl; //666
return 0;
}
1.8 数组的引用
1> C++中不支持引用数组
2> C++中支持数组的引用
3> 定义格式: 数据类型 (&引用名) [数组长度] = 数组名;
#include <iostream>
using namespace std;
//定义fun1函数
void fun1(int arr[], int n)
{
cout<<sizeof(arr)<<endl; //8
cout<<"数组目前中的数据为:";
for(int i=0; i<n; i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
}
//定义fun2函数
void fun2(int *arr, int n)
{
cout<<sizeof(arr)<<endl; //8
cout<<"数组目前中的数据为:";
for(int i=0; i<n; i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
}
//定义fun3函数
void fun3(int (&arr)[8], int n)
{
cout<<sizeof(arr)<<endl; //32
cout<<"数组目前中的数据为:";
for(int val:arr)
{
cout<<val<<" ";
}
cout<<endl;
}
int main()
{
int arr[8] = {1,3,5,8,7,6,4,2};
//调用函数传递该数组
fun3(arr, 8);
return 0;
}
1.9 右值引用(了解)
1> 上面描述的引用都是左值引用
2> 左值:有内存空间的容器称为左值,表现形式有变量、堆区空间、字符串常量
右值:没有内存空间的数据,表现形式有常量、表达式的结果、值返回函数的返回值、将亡值
3> 右值引用的定义格式:数据类型 &&引用名 = 引用目标;
4> 左值引用的目标必须是一个左值,右值引用的目标必须是一个右值
5> 可以通过函数 move将左值转变成右值
#include <iostream>
using namespace std;
int main()
{
int num = 520; //其中num为左值 520为右值
int &ref1 = num; //定义左值引用引用左值的空间
int &&ref2 = 520; //定义右值引用引用右值的空间
//int &ref3 = 520; //左值引用不能绑定右值
//int &&ref4 = num; //右值引用不能绑定一个左值
int &ref5 = ref2; //定义一个左值引用,引用一个右值的引用
int &&ref6 = move(num); //将左值移动成右值,进行使用
return 0;
}
1.10 指针和引用的区别(重要)
1> 指针定义时需要使用*号,引用定义时需要使用&
2> 指针取值需要使用*号运算符完成,引用使用时直接跟目标使用方式一致
3> 指针定义时,需要给指针分配内存空间8字节,引用定义时不需要分配内存空间,引用使用的是目标的空间
4> 指针初始化后,可以改变指针的指向,但是引用初始化后,不能在改变目标了
5> 指针有二级指针,但是引用没有二级引用
6> 有空指针,但是没有空引用
7> 指针进行偏移运算时是对内存地址的偏移,而引用进行偏移时,就是对目标值的偏移
8> 指针不能指向右值,但是右值引用的目标可以是右值
9> 指针定义时可以不初始化(野指针),引用定义时必须初始化
10> 指针可以有指针数组,但是引用不能定义引用数组
2. 堆区空间的申请和释放
2.1 概述
1> 在C语言中,对于堆区空间的申请和释放,使用的是malloc和free函数,C++中也可以继续使用
2> 在C++中,提供了更加操作方便的关键字 new和delete用于堆区空间的申请和释放
2.2 new和delete
1> new和delete申请和释放堆区空间时,分为单个空间的申请和释放以及连续空间的申请和释放
2> 单个空间的申请和释放
申请: 数据类型 * 指针名 = new 数据类型;
释放: delete 指针名;
#include <iostream>
using namespace std;
int main()
{
//从堆区申请一个int类型的空间数据
int *p1 = new int;
cout<<"*p1 = "<<*p1<<endl; //随机值
*p1 = 520; //使用堆区空间
cout<<"*p1 = "<<*p1<<endl; //520
// 从堆区空间申请double类型的数据
double *p2 = new double(3.14);
cout<<"*p2 = "<<*p2<<endl; //3.14
//释放堆区空间
delete p1;
delete p2;
return 0;
}
3> 连续空间的申请和释放
申请: 数据类型 *指针名 = new 数据类型[元素个数];
释放:delete []指针名;
#include <iostream>
using namespace std;
int main()
{
//从堆区申请一个int类型的空间数据
int *p1 = new int;
cout<<"*p1 = "<<*p1<<endl; //随机值
*p1 = 520; //使用堆区空间
cout<<"*p1 = "<<*p1<<endl; //520
// 从堆区空间申请double类型的数据
double *p2 = new double(3.14);
cout<<"*p2 = "<<*p2<<endl; //3.14
//释放堆区空间
delete p1;
delete p2;
cout<<"***********************************************"<<endl;
//连续申请5个空间的int类型
int *p3 = new int[5]; //没有初始化,默认都是随机值
for(int i=0; i<5; i++)
{
cout<<p3[i]<<" ";
}
cout<<endl;
//连续申请空间并初始化
int *p4 = new int[5]{3,7,2,1,6};
for(int i=0; i<5; i++)
{
cout<<p4[i]<<" ";
}
cout<<endl;
//释放空间
delete []p3;
delete []p4;
return 0;
}
2.3 new\delete与malloc\free的区别
1> new和delete是关键字,而malloc和free是函数,需要包含相关的库
2> 使用new申请空间后,申请什么类型就是什么类型的地址,而malloc申请的结果是void*需要具体转换
3> new申请空间时,可以初始化,malloc申请空间时不能初始化
4> new和delete申请和释放空间时,单个和连续的操作是不同的,而malloc和free是不区分单个和连续申请释放的
5> new申请空间时以数据类型为单位,而malloc申请空间时以字节为单位
6> new申请空间时会自动计算所需空间的大小,而malloc申请空间时需要手动计算大小
7> new关键字中封装了malloc函数,delete关键字中封装了free
9> new在申请对象的空间时会调用该类的构造函数,malloc不会
10> delete在释放对象空间时,会自动调用该类的析构函数,free不会
3. C++中的结构体
1> C语言中的结构体仅仅只是属性的聚合体,都只能封装一些变量
2> C++中的结构体,可以包罗万象(变量、函数、类型)
3> C语言中结构体定义变量时需要加上struct,而C++中不需要加struct
4> C语言中的结构体在声明时,是不允许给成员变量初始值的, 而C++中的结构体可以
5> C语言中的结构体时不能继承的,C++中的结构体可以继承
6> C语言中的结构体中所有成员变量都是公共的,外界可以通过结构体变量进行访问,而C++中的结构体中的成员变量是可以加访问权限的,分为公共权限、受保护权限、私有权限。只有公共权限的外界能够访问
#include <iostream>
using namespace std;
struct Person
{
//如果不给设置权限,默认都是公共权限
string name; //姓名
int age = 100; //年龄
//C++中的结构体,可以封装函数
void speaker(); //结构体内声明,结构体外定义
private: //该关键字后面的变量或者函数属于私有权限
int money = 10000;
public:
void set_money(int m); //结构体内声明
protected:
string skill = "C++"; //技能
};
//结构体外定义成员函数
void Person::speaker()
{
cout<<"name = "<<name<<" age = "<<age<<" money = "<<money<< " skill = "<<skill<<endl;
}
void Person::set_money(int m)
{
money = m;
}
/***************************************上面是person的结构体的内容*************************************************/
struct Student : Person //定义一个学生结构体,继承自Person结构体
{
private:
double score; //学生类型扩充的私有成员
public:
void study()
{
cout<<"good good study day day up!!!!"<<" I am studding "<<skill<<endl;
//cout<<"money = "<<money<<endl;
}
};
int main()
{
//使用结构体类型定义变量
//struct Person p1;
Person p1; //定义结构体变量时可以不用加struct
p1.age = 20;
p1.name = "zhangpp";
p1.speaker();
//cout<<p1.money<<endl; //私有成员外部无法直接访问,需要在结构体中提供公共的接口来操作
p1.set_money(111111111);
p1.speaker();
//cout<<p1.skill<<endl; //受保护的属性在外部无法直接访问,但是,在结构体内和子结构体中可以访问
cout<<"*******************************************************"<<endl;
Student s1; //定义一个学生类型的结构体变量
s1.name = "zhangsan";
s1.speaker();
s1.study();
return 0;
}
课外作业:
使用C++手动封装一个顺序表,包含成员数组一个,成员变量N个
#include <iostream>
using namespace std;
using datatype = int;
struct Seqlist
{
private:
datatype *data;
int len = 0;
int size = 0;
public:
void init(int s);
bool empty();
bool full();
bool add(datatype e);
int length();
bool insert_pos(datatype e,int pos);
bool delete_pos(int pos);
datatype &at_pos(int index)
{
return data[index];
}
void expend();
void delete_list();
};
//申请空间函数
void Seqlist::init(int s)
{
size = s; // 设置顺序表的大小
data = new datatype(size); // 新建一个具有指定大小的数据存储空间
}
//表判空函数
bool Seqlist::empty()
{
return (len == 0);
}
//表判满函数
bool Seqlist::full()
{
return (len == size);
}
//添加表的数据函数
bool Seqlist::add(datatype e)
{
if(full())
{
expend(); //如果满了就申请两倍的空间
}
data[len]=e; //赋值给data
len++; //长度加1
return true;
}
int Seqlist::length()
{
return len; //返回表的长度
}
//插入函数
bool Seqlist::insert_pos(datatype e,int pos)
{
// 检查插入位置是否有效,如果位置无效或列表已满,则插入失败
if (pos < 0 || pos > len || full())
{
return false;
}
// 为插入的新元素腾出空间,需要将插入位置之后的元素依次后移
for (int i = len - 1; i > pos; i--)
{
data[i + 1] = data[i];
}
// 在指定位置插入新元素
data[pos] = e;
// 更新列表长度
len++;
// 插入成功
return true;
}
//删除函数
bool Seqlist::delete_pos(int pos)
{
// 检查删除位置是否有效,如果位置无效或列表为空,则插删除失败
if(pos<0 || pos>len || empty())
{
return false;
}
// 找到删除的位置,从后往前覆盖对应的值
for(int i=pos;i<len-1;i++)
{
data[i]=data[i+1];
}
//更新列表长度
len--;
return true;
}
//扩充空间函数
void Seqlist::expend()
{
size *=2; //空间乘2
datatype *newdata = new datatype(size); //申请新的空间
for(int i=0;i<len;i++)
{
newdata[i] = data[i]; //将旧空间的值插入到新空间内
}
delete [] data; //删除旧列表数据
data = newdata;
}
void Seqlist::delete_list()
{
delete [] data;
}
int main()
{
// 创建一个Seqlist对象q
Seqlist q;
// 初始化链表q,设置初始容量为4
q.init(4);
// 向链表中添加元素
q.add(4);
q.add(6);
q.add(8);
q.add(12);
q.add(12);
// 在索引3处插入数值20
q.insert_pos(20,3);
// 删除索引为2的元素
q.delete_pos(2);
// 输出链表的当前长度
cout<<"链表长度: "<<q.length()<<endl;
// 输出索引为3的元素值
cout<<"第3个位置数据为"<<q.at_pos(3)<<endl;
// 清空整个链表
q.delete_list();
return 0;
}