作业
1> 自行封装一个栈的类,包含私有成员属性:栈的数组、记录栈顶的变量
成员函数完成:构造函数、析构函数、拷贝构造函数、入栈、出栈、清空栈、判空、判满、获取栈顶元素、求栈的大小
02stack.h:
cpp
#ifndef __02STACK_H__
#define __02STACK_H__
#include <iostream>
#define MAX 10 //栈的容量
using namespace std;
class Stack{
private:
int *data; //栈的数组
int top; //栈顶下标
public:
//构造函数
Stack();
//析构函数
~Stack();
//拷贝构造函数
Stack(const Stack &other);
//判空
bool empty();
//判满
bool full();
//入栈
int push();
//出栈
int pop();
//清空栈
int clear();
//获取栈顶元素
int get_top_value();
//求栈的大小
int size();
};
#endif // 02STACK_H
02stack.cpp:
cpp
#include "02stack.h"
//构造函数
Stack::Stack():data(new int[MAX]),top(-1){ //栈顶从-1开始
cout<<"初始化成功!"<<endl;
//cout<<"构造函数"<<endl;
}
//析构函数
Stack::~Stack(){
delete []data;
data = nullptr;
//cout<<"析构函数"<<endl;
}
//拷贝构造函数
Stack::Stack(const Stack &other){
top = other.top;
data = new int[MAX];
for(int i = 0;i <= top;i++){
data[i] = other.data[i];
}
cout<<"拷贝成功!"<<endl;
//cout<<"拷贝构造函数"<<endl;
}
//判空
bool Stack::empty(){
return top == -1;
}
//判满
bool Stack::full(){
return top >= MAX-1;
}
//入栈
int Stack::push(){
if(full()){
cout<<"栈满!入栈失败!"<<endl;
return -1;
}
int value;
cout<<"请输入您要入栈的元素:";
cin>>value;
top++;
data[top] = value;
cout<<value<<" 入栈成功!"<<endl;
//cout<<"top = "<<top<<endl;
return 0;
}
//出栈
int Stack::pop(){
if(empty()){
cout<<"栈空!出栈失败!"<<endl;
return -1;
}
int temp = data[top];
data[top] = 0;
top--;
cout<<temp<<" 出栈成功!"<<endl;
return 0;
}
//清空栈
int Stack::clear(){
data[top] = 0;
while (top-->0) {
data[top] = 0;
}
cout<<"清空栈成功!"<<endl;
return 0;
}
//获取栈顶元素
int Stack::get_top_value(){
cout<<"栈顶元素为:"<<data[top]<<endl;
return 0;
}
//求栈的大小
int Stack::size(){
cout<<"栈的大小为:"<<top+1<<endl;
return 0;
}
main.cpp:
cs
#include "02stack.h"
int main()
{
Stack s;
s.push();
s.push();
s.push();
s.get_top_value();
s.size();
Stack s1 = s;
s1.size();
s1.get_top_value();
s.pop();
s.clear();
s.size();
return 0;
}
效果图:
2> 自行封装一个循环顺序队列的类,包含私有成员属性:存放队列的数组、队头位置、队尾位置
成员函数完成:构造函数、析构函数、拷贝构造函数、入队、出队、清空队列、判空、判满、求队列大小
03queue.h:
cpp
#ifndef __03QUEUE_H__
#define __03QUEUE_H__
#include <iostream>
#define MAX 10
using namespace std;
class Queue{
private:
int front;
int back;
int *data;
public:
//构造函数
Queue();
//析构函数
~Queue();
//拷贝构造函数
Queue(const Queue &other);
//判空
bool empty();
//判满
bool full();
//入队
int push();
//出队
int pop();
//清空队列
int clear();
//求队列大小
int size();
};
#endif // 03QUEUE_H
03queue.cpp:
cpp
#include "03queue.h"
//构造函数
Queue::Queue():front(0),back(0),data(new int[MAX]){
cout<<"初始化成功"<<endl;
cout<<"构造函数"<<endl;
}
//析构函数
Queue::~Queue(){
delete []data;
data = nullptr;
cout<<"析构函数"<<endl;
}
//拷贝构造函数
Queue::Queue(const Queue &other){
front = other.front;
back = other.back;
data = new int[MAX];
if(front < back){
for(int i = front;i < back;i++){
data[i] = other.data[i];
}
}
if(front > back){
for(int i = front;i < back+MAX;i++){
data[i%MAX] = other.data[i%MAX];
}
}
//cout<<"q1.front = "<<front<<endl;
//cout<<"q1.back = "<<back<<endl;
//cout<<"front = "<<other.front<<endl;
//cout<<"back = "<<other.back<<endl;
cout<<"拷贝构造函数"<<endl;
}
//判空
bool Queue::empty(){
return front == back;
}
//判满
bool Queue::full(){
return (back+1)%MAX == front;
}
//入队
int Queue::push(){
if(full()){
cout<<"队列已满!入队失败!"<<endl;
return -1;
}
int value;
cout<<"请输入您要入队的元素:";
cin>>value;
data[back] = value;
back = (back+1)%MAX;
//cout<<"front = "<<front<<endl;
//cout<<"back = "<<back<<endl;
cout<<value<<" 入队成功!"<<endl;
return 0;
}
//出队
int Queue::pop(){
if(empty()){
cout<<"队列已空!出队失败!"<<endl;
return -1;
}
int temp = data[front];
data[front] = 0;
front = (front+1)%MAX;
//cout<<"front = "<<front<<endl;
//cout<<"back = "<<back<<endl;
cout<<temp<<" 出队成功!"<<endl;
return 0;
}
//清空队列
int Queue::clear(){
if(front < back){
for(int i = front;i < back;i++){
data[i] = 0;
}
}
if(front > back){
for(int i = front;i < back+MAX;i++){
data[i%MAX] = 0;
}
}
front = back = 0;
//cout<<"front = "<<front<<endl;
//cout<<"back = "<<back<<endl;
cout<<"清空队列成功!"<<endl;
return 0;
}
//求队列大小
int Queue::size(){
cout<<"队列大小为:"<<(back+MAX-front)%MAX<<endl;
return 0;
}
main.cpp:
cpp
#include "03queue.h"
int main()
{
Queue q;
q.push();
q.push();
q.push();
q.size();
Queue q1 = q;
q1.size();
q.pop();
q.pop();
q.size();
q.push();
q.clear();
return 0;
}
效果图:
一、类
1.1 类的定义格式
cpp
class 类名
{
public:
公共的成员变量、成员函数的定义;
protected:
受保护的成员变量、成员函数;
private:
私有的成员变量、成员函数;
};
//注意
1、类中是有访问权限控制
public该权限下的成员,可以在类内、子类中、类外被访问
protected该权限下的成员,可以在类内、子类中被访问,类外不能被访问
private:该权限下的成员可以在类内被访问,子类中和类外都无权访问
2、类中的访问权限是针对于类体而言的,跟类对象没有关系
3、如果没有给定访问权限,默认是私有的访问权限
4、每个访问权限可以出现多次,可以出现在类内的任何位置,每种访问权限的作用域是从该关键字开始到下一个关键字或整个类体结束为止
5、我们通常将某个权限下的所有成员放到一个访问权限关键字下
6、通常成员属性一般定义成私有权限,成员函数一般定义成公有权限
7、类中的成员函数可以访问类中的任意权限下的成员,包括私有成员
1.2 类的大小
1> 一个空类的大小为1字节,起到占位作用,如果有成员属性,则会将这一字节分配给成员属性
2> 类的大小也跟结构体一样遵循字节对齐
3> 类中的普通成员函数不占类的大小,即使成员函数中有变量
4> 类中如果存在虚函数,那么会默认提供一个指针的大小(后期讲)
1.3 C++中结构体与类的区别
1> C++中结构体跟类没什么太大区别,主要的区别是默认访问权限和默认继承方式不同
2> C++中结构体的默认访问权限是public,而类的默认访问权限是private
3> C++中结构体的默认继承方式是public,而类的默认继承方式是private
4> 使用场景:一般实现数据结构的相关操作,我们使用结构体完成,其他有关类对象的实现,我们使用类来完成
二、this指针
2.1 引入目的
在类的定义过程中,有可能需要使用一个变量来指代类对象本身,用来找该类中的成员属性或成员变量,此时C++编译器会给类中所有非静态成员函数提供一个隐藏的形参指针this,表明指代该类本身,哪个对象使用我,我就指向哪个对象的起始地址。
2.2 this的原型推导
1> 原型:类名 * const this;
2> 可以通过this指针更改类中的内容,但是不能更改this的指向
3> 只有成员函数中才能使用this指针,是每个非静态成员函数都拥有的一个隐藏形参
2.3 必须使用this的场景
1> 当非静态成员函数中,函数的形参名或局部变量名与类的成员名同名时,在函数体内必须使用this指针加以区分,否则使用的是局部变量
2> 在拷贝赋值函数中,需要返回自身的引用时,需要使用this指针
三、类中特殊的成员函数
当定义一个类时,编译器会给类体提供一些特殊的函数。
特殊原因:如果没有显性定义这些函数,系统会默认提供这些函数,如果显性定义了这些函数,系统就不再提供默认的了
这些函数无需显性调用,特殊时期系统会自动调用
特殊函数:构造函数、析构函数、拷贝构造、拷贝赋值、移动构造、移动赋值、取地址运算符重载、常取地址运算符重载
3.1 构造函数
1> 功能:使用类实例化对象时,用于给类对象申请资源并初始化用的
2> 定义格式
cpp
1、函数名与类同名
2、没有返回值,连void都没有
3、权限:一般为public
4、参数:可以有也可以没有参数,构造函数可以重载
5、格式:
类名(形参列表){函数体内容}
3> 调用时机
在使用类实例化对象过程中,系统自动调用该函数,无需手动调用
栈区:实例化对象时,系统自动调用构造函数
类名 对象名(实参列表); //此时系统会自动调用构造函数
堆区:定义类对象指针时,不会调用构造函数,当使用new关键字给类对象申请空间时会自动调用构造函数
类名 *指针名; //此时不会调用构造函数
指针名 = new 类名(实参列表); //此时才会调用构造函数
4> 注意:
1、如果类内没有显性定义任何构造函数,系统会默认提供一个无参构造函数,
2、如果显性定义了任意一种有参构造函数,系统就不再提供无参构造函数了,如果非要使用无参构造函数,需要显性再定义出无参构造函数
3、 一个类中,可以定义多个构造函数,这多个构造函数构成重载关系
4、构造函数的也支持默认参数
cpp
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int age;
double score;
public:
//显性定义无参构造函数
Stu()
{
cout<<"Stu::无参构造"<<endl;
}
Stu(string n)
{
name = n; //给成员变量n进行赋值
cout<<"Stu::一个参数的构造函数"<<endl;
}
//带默认参数的构造函数
Stu(string n, int a, double s = 90)
{
name = n;
age = a;
score = s;
cout<<"Stu::所有参数的构造函数"<<endl;
}
//定义成员函数
void show()
{
cout<<"name = "<<name<<endl;
cout<<"age = "<<age<<endl;
cout<<"score = "<<score<<endl;
}
};
int main()
{
Stu s1; //在栈区实例化一个学生对象
s1.show(); //随机值
cout<<"***************************************"<<endl;
Stu *p1; //不会调用构造函数
p1 = new Stu; //此时会调用构造函数
p1->show();
cout<<"***************************************"<<endl;
Stu s2("zhangpp");
s2.show();
cout<<"***************************************"<<endl;
Stu s3("zhangsan", 18);
s3.show();
return 0;
}
5> 构造函数可以设置初始化列表
1、使用格式:在构造函数的形参列表后,由冒号引出初始化列表,完成初始化工作
类名(类型1 形参1, 类型2 形参2,。。。,类型n 形参n):成员1(形参1),成员2(形参2),。。。,成员n(参数n)
{
函数体内容;
}
2、注意:在初始化列表中,括号内是形参数据,括号外是成员数据,不要写反
6> 构造函数必须使用初始化列表的情况
1、当类中的成员有引用成员时,必须在初始化列表中对该引用成员进行初始化
2、当类中有const修饰的成员变量时,对该成员的的初始化也必须放在初始化列表中
3、当构造函数的形参名和成员变量名同名时,可以使用初始化列表来解决初始化问题
4、当类中有其他类的对象作为成员变量时,对该类对象的初始化工作也必须在初始化列表中完成,需要在初始化列表中显性调用成员对象的有参构造完成初始化工作,如果没有显性调用成员对象的有参构造,系统会自动调用该成员对象的无参构造来完成对该成员对象的初始化工作
3.2 析构函数
1> 功能:在类对象消亡时,用于回收类对象的资源空间用的
2> 定义格式
cpp
1、函数名是在类名前加~:~类名
2、参数:无
3、返回值:没有
4、权限:一般为public
5、格式:
~类名(){}
3> 调用时机:
栈区:类对象的作用域结束时,系统会自动调用该类的析构函数,完成对对象的内存回收。
堆区:何时使用delete何时调用析构函数,一直不调用,一直不析构
4> 注意:
1、如果类中没有显性定义析构函数,系统会默认提供一个析构函数完成对成员内存的回收工作
2、如果类中显性定义了析构函数,系统就不再提供,默认的析构函数了
3、构造和析构的顺序:
栈区:先构造的后析构,后构造的先析构(在同一作用域下)
堆区:何时使用delete何时调用析构函数
4、如果类中没有指针成员,那么使用系统提供的析构函数就足够完成对类对象的内存回收
如果类中有指针成员,并指向堆区空间,需要显性定义析构函数,在析构函数的函数体内,将成员指针指向的空间进行释放,否则会内存泄漏
5、一个类中析构函数只能有一个
3.3 拷贝构造函数
1> 拷贝构造函数是一个特殊的构造函数,功能是:使用一个类对象给另一个类对象初始化时,系统自动调用
string s1("hello world"); //有参构造
string s2 = s1; //调用拷贝构造
string s3(s2); //拷贝构造函数
2> 定义格式
cpp
1、函数名与类同名
2、没有返回值
3、参数:同类的其他类对象的引用
4、权限:一般为public
5、格式:
类名(const 类名 &other){}
3> 调用时机:
1、使用一个类对象给另一个类对象初始化时,系统自动调用
2、函数形参时类对象时,实参向形参传递时,也会调用拷贝构造函数
3、函数返回值是类对象时,函数调用时也会调用拷贝构造函数
4> 注意事项:
1、如果没有显性定义拷贝构造函数,系统会默认提供一个拷贝构造函数,来完成对象成员之间的简单赋值
2、如果显性定义了拷贝构造函数,系统就不再提供默认的拷贝构造函数了
5> 深浅拷贝
1、在一个类中,如果没有显性定义拷贝构造函数,系统会默认提供一个拷贝构造函数,来完成类对象之间的简单赋值,这个拷贝构造函数是一个浅拷贝,如果类中无指针成员,那么使用该拷贝构造函数没有问题
2、如果类中有指针成员并且指向堆区空间,使用浅拷贝就会造成对同一个堆区空间的多次释放问题,会产生double free的段错误
3、此时就需要显性定义拷贝构造函数完成深拷贝:在拷贝构造函数的初始化列表中,给自己的指针成员重新申请出一个空间,将原对象的指针成员指向的空间内容赋值过来,即可完成深拷贝
cpp
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int age;
double score;
public:
//显性定义无参构造函数
Stu()
{
cout<<"Stu::无参构造"<<endl;
}
//带默认参数的构造函数
Stu(string n, int a, double s ):name(n),age(a),score(s)
{
cout<<"Stu::有参构造函数"<<endl;
}
//定义析构函数
~Stu()
{
cout<<"Stu::析构函数:"<<this<<endl;
}
//定义拷贝构造函数
Stu(const Stu &other):name(other.name), age(other.age), score(other.score)
{
cout<<"拷贝构造函数"<<endl;
}
//定义成员函数
void show()
{
cout<<"name = "<<name<<endl;
cout<<"age = "<<age<<endl;
cout<<"score = "<<score<<endl;
}
};
//定义一个全局函数,实参向形参传递时调用拷贝构造,函数返回时也会调用拷贝构造
Stu fun(Stu s) //int num = 520;
{
s.show();
return s;
}
int main()
{
Stu s1("zhangpp", 18, 90); //调用有参构造函数
fun(s1);
return 0;
}