C++ 类和对象

面向过程/面向对象

C语言是面向过程,关注过程,分析出求解问题的步骤,通过函数调用逐步解决问题

C++是基于面对对象的,关注的是对象——将一件事拆分成不同的对象,依靠对象之间的交互完成

引入

C语言中结构体只能定义变量,但是在C++中,结构体不仅可以定义变量,也能定义函数

以栈为例,C++里对于栈的函数可以这样子写:

//C++
struct Stack
{//成员变量int* a;int top;int capacity;//成员函数——直接将函数与成员变量放在一起定义void Init(){a = nullptr;top = capacity = 0;}void Push(int x){}
};//C
void StackInit(struct Stack* ps)
{}
void StackPush(struct Stack* ps,int x)
{}

类的定义

class className
{//类体:由成员函数和成员变量组成};//有分号

类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者

成员函数。

两种定义方式

1.声明和定义全部放在类体中

class Date
{
public:void Init(int year, int month, int day){//域搜索,一个是类域的year,一个是函数局部域的year//所以需要加上_来区分_year = year;_month = month;_day = day;}
private:int _year;//属于声明int _month;int _day;
};

2.类声明放在.h中,成员函数定义在.cpp文件中,且成员函数名前需要加类名

//.h
class Stack
{
public://成员函数void Init();void Push(int x);int Top();
private://访问限定符,访问到下一个访问限定符或者到函数末尾才会结束//这样在主函数里就无法访问int* a;int top;int capacity;
};//.cpp
void Stack::Init()//全局函数,需要在其他文件里去找,而不是到类域里去找
{a = nullptr;top = capacity = 0;
}
void Stack::Push(int x)//需要有访问符
{if (top == capacity){size_t newcapacity = capacity == 0 ? 4 : capacity * 2;a = (int*)realloc(a, sizeof(int) * newcapacity);capacity = newcapacity;}a[top++] = x;
}
int Stack::Top()
{return a[top - 1];
}

访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用。

说明

1. public修饰的成员在类外可以直接被访问

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) 3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

4. 如果后面没有访问限定符,作用域就到 } 即类结束。

5. class的默认访问权限为private,struct为public(因为struct要兼容C)

访问

以Date类为例

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year<<" ";cout << _month << " ";cout << _day << " ";cout << endl;}
//private:int _year;//因为此处是声明,不是定义//声明特点:只有一个躯体,不能访问,没有分配空间int _month;int _day;
};

1.由于private中对于成员变量,是声明而不是定义——故不能直接访问,需要先建立个对象

int  main()
{Date d;d._year=1//errreturn 0;    
}

无法访问——因为类中_year被private访问限定符限制住了,所以不能在类外访问

如何解决——将' private '注释掉

2.类外调用函数

若是想调用函数,首先就需要定义一个对象d——然后在用d.函数名()去访问

例如此处的打印函数,想打印类的内容

int main()
{Date d;d1.Init(2022,2,22);//先初始化再打印d1.Print();return 0;
}

类的大小(存储方式)

同理,我们对于日期类Date进行讨论

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year<<" ";cout << _month << " ";cout << _day << " ";cout << endl;}
private:int _year;int _month;int _day;
};

进行大小的打印

int main()
{Date d1;cout<<sizeof(d1)<<endl;cout<<sizeof(Date)<<endl;return 0;
}

类的大小仅取决于成员变量,成员函数存放在公共代码区中,不会计入类的大小,仅对象的成员变量会算入大小

 若成员变量不为同类型呢?

class A4
{
public:void f1() {}private:char _c;int _a;
};

为8——同理按照编译器内存对齐的规律来进行的 

类的实例化

用类类型创建对象的过程,称为类的实例化

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;

2. 一个类可以实例化出多个对象,实例化出的对象才占用实际的物理空间,存储类成员变量

int main()
{Date._year=1;//errreturn 0;
}

this指针

现在对于一个日期的类Date

class Date
{
public:void Init(int year,int month,int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;//声明,不是定义int _month;int _day;
};int main()
{Date d1;d1.Init(2022,3,15);Date d2;d2.Init(2023,3,15);return 0;
}

对于Init 和 Print 函数,看起来分别有3个和0个参数,但事实并非如此——分别有4个和1个,为什么?

这两个函数内部还有一个隐含参数——this指针,会将函数进行一个修改:

 同理,对于主函数里的初始化函数:

 建立联系

 所以函数可以写成

void Init(int year,int month,int day){this->_year = year;this->_month = month;this->_day = day;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}

注意!!!!

this指针在实参和形参位置不能写,但是在类里可以直接写出来使用(但没必要)-指向当前对象

若在函数里嵌套另一个成员函数呢?指向两个Print函数的this指针是否一样?

void Init(int year,int month,int day){this->_year = year;this->_month = month;this->_day = day;Print();}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}

不一样——一个对象中,两个this指针是各自的不同的指针变量,但是指向的地方是一样的。

若是两个不同的对象,那么这两个this指针不仅不同,指向的空间也不同

特性

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

2. 只能在“成员函数”的内部使用

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给

this形参。所以对象中不存储this指针。

4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递

两个例题

1

有以下代码

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}

由于定义了一个对象p,但是为空指针,要去调用成员函数——需要在对象里找吗?

不,成员函数定义在公共函数区域里,所以可以直接调用

2

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:void PrintA() {cout<<_a<<endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}

此处运行崩溃——因为p是空指针,且要访问_a,需要用到this指针去访问this->_a

但此处this指针为空,所以会导致运行崩溃

综上,类的访问都是先传递this指针,再去找到函数的地址并且使用函数

this指针指向的是需要作用的对象

类的6个默认成员函数

若有

class Date{};

那么这个类成为空类——但是这个类里什么都没有吗????

实际上,编译器会自动生成以下6个默认成员函数

构造函数

构造函数负责初始化,函数名就是类名

特征

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

class Date
{
public://正常初始化void Init(int year, int month, int day){_year = year;_month = month;_day = day;}//构造函数Date()//初始化{_year=1;_month=1;_day=1;}Date(int year, int month, int day)//直接输入,用缺省参数{_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;   
};

为了方便,我们可以直接将两个构造函数合并

Date(int year=1, int month=1, int day=1)//直接输入,用全缺省参数,减少了代码量{_year = year;_month = month;_day = day;}

且考虑到其他函数内部会没有定义的值,所以可以在声明成员变量的时候,给予其缺省值

故类可以修改为

class Date
{
public:Date(int year=1, int month=1, int day=1)//直接输入,用全缺省参数,减少了代码量{cout << "Date(int year=1, int month=1, int day=1)" << endl;_year = year;_month = month;_day = day;}void Print()//实际上为1个参数{cout << _year << "/" << _month << "/" << _day << endl;//cout << this << endl;}
private:int _year;int _month;int _day;
};

如果用户显式定义了构造函数,那么编译器就不会生成默认的构造函数

构造函数的出现,减少了代码量

int main()
{   //非构造Date d1;d1.Init(2022,2,22);Date d2(2022,2,22);return 0;
}

一行代码就能实现对象的创建和初始化

注意

不实现构造函数的情况下,编译器会生成默认的构造函数,虽然日期类Date对象d调用了默认构造函数但是其成员变量_year/_month/_day仍然为随机值,这是为什么?

——C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型。

因此,为了解决对于内置类型成员不初始化的缺陷,我们可以在成员变量声明时给予默认值

private:int _year=1;int _month=1;int _day=1;

析构函数

与构造函数相反,析构函数是用于对于对象中资源的清理

特征

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

同理以Date为例

class Date
{
public:Date(int year=1, int month=1, int day=1)//直接输入,用全缺省参数,减少了代码量{cout << "Date(int year=1, int month=1, int day=1)" << endl;_year = year;_month = month;_day = day;}void Print()//实际上为1个参数{cout << _year << "/" << _month << "/" << _day << endl;//cout << this << endl;}~Date(){cout << "~Date()" << endl;}
private:int _year=1;//声明,默认会用给的缺省值进行初始化,内置类型不作处理int _month=1;//作为缺省值,会补充其他函数里没有定义的值int _day=1;
};

若不写析构函数,那么编译器就会默认生成析构函数来进行资源的清理

以栈来展现构造和析构函数

class Stack
{
public:/*Stack(){a = nullptr;top = capacity = 0;}*/Stack(size_t n = 4)//不传入值则就默认4个空间{cout << "Stack(size_t n = 4)" << endl;if (n == 0){a = nullptr;top = capacity = 0;}else{a = (int*)malloc(sizeof(int) * n);if (a == nullptr){perror("realloc fail");exit(-1);}top = 0;capacity = n;}}~Stack(){cout << "~Stack()" << endl;free(a);a = nullptr;top = capacity = 0;}void Push(int x){if (top == capacity){size_t newcapacity = capacity == 0 ? 4 : capacity * 2;int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);if (tmp == nullptr){perror("realloc fail");exit(-1);}if (tmp = a){cout << capacity << "原地扩容" << endl;//原地扩容还是异地扩容呢?//若打印就原地,否则就异地}else{cout << capacity << "异地扩容" << endl;}a = tmp;capacity = newcapacity;}a[top++] = x;}int Top(){return a[top - 1];} void Pop(){assert(top > 0);--top;}void Destroy(){free(a);a = nullptr;top = capacity = 0;}bool Empty(){if (top == 0)return 1;return 0;}
private:int* a;int top;int capacity;
};

在构造函数处,我们选择用了缺省参数,这样可以随机初始化自己想要的栈的大小

int main()
{Stack st1;Stack st2(10);return 0;
}

 那么,对于类中这种定义的函数,我们可以这样子调用

int main()
{    Stack st2(10);st2.Push(1);st2.Push(2);st2.Push(3);st2.Push(4);st2.Push(5);st2.Push(6);st2.Push(7);st2.Push(8);st2.Push(9);st2.Push(10);while (!st2.Empty()){cout << st2.Top() << " ";st2.Pop();}cout << endl;return 0;
}

也可以直接传入1000的量,这样子无需要扩容

    Stack st2(1000);    for (size_t i = 0; i < 1000; i++){st2.push(i);}

注意:

1.会自动构造 
2.后定义,先析构,若没有写析构函数,那么编译器就会默认生成一个析构函数
3.内置类型成员不处理,但是会自定义类型成员会调用在这个成员的析构函数

但是栈里一定要写析构函数!!!——析构函数是浅处理,就是处理像int /char 这种普通类型的变量,随着函数的结束就会销毁,但是栈里有定义——int* a,也就是数组,需要手动处理,不然会导致内存泄漏的问题!!!!

特殊

class MyQueue
{
private:Stack _pushst;//初始化的时候已经生成Stack _popst;};

对于此类,尽管有int* a指针定义,也需要写——因为定义Stack的时候就已经生成了相关的构造和析构函数,此时就不需要我们去写

除此之外,构造函数和析构函数的调用就类似于栈,先构造的最后析构

拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。

特征:

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用

无穷递归错误情况代码:

class Date
{
public:Date(Date d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};

 同理,在自定义函数里,也不能直接传值传入

void func2(Stack s)
{s.Push(1);s.Push(2);
}

这里直接传参的话,那么就是用形参s接收一个对象,此时对象和形参都指向一个空间,那么函数结束后形参指向的空间会进行销毁,那么原本对象指向的空间将会不存在,会导致报错(析构两次)

那怎么解决?——用引用,获取对象的地址 ,改为如下:

class Date
{
public:Date(Date& d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};void func2(Stack& s)
{s.Push(1);s.Push(2);
}

如何定义?

——有' () '  也有 ' = ' 两种形式

int main()
{Date d1(2022,2,22);Date d2(d1);Date d3=d1;d1.Print();d2.Print();d3.Print();return 0;
}

先构造d1,然后直接将d1拷贝构造给d2和d3,最后再逐一析构

若不写拷贝构造函数呢?

—— 编译器会自己生成

class Date
{
public://Date(Date& d) //{// _year = d._year;//_month = d._month;//_day = d._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022,2,22);Date d2=d1;//passreturn 0;
}

 但是对于栈,若不写拷贝构造函数——则会报错err

Stack s1;
Stack s2=s1;

 所以对于栈一定要进行自定义拷贝函数

Stack(const Stack& s)//用const防止代码赋值写反导致改变了原对象的内容{a = (int*)malloc(sizeof(int) * s.capacity);if (a == NULL){perror("malloc申请失败");return;}memcpy(a, s.a, sizeof(int) * s.top);capacity = s.capacity;top = s.top;}

总结:

1.内置类型,值拷贝
2.自定义类型,调用他的拷贝
总结:日期类Date 不需要我们自己实现拷贝构造,默认生成就可以用
          栈类Stack 需要我们自己实现深拷贝的拷贝构造,默认生成会出现问题
          因为Stack需要释放空间,防止栈溢出内存泄漏,但是Date里的变量会随着空间的销毁而销毁。

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数原型:返回值类型 operator操作符(参数列表)

再次以日期类Date为例

实现日期类的比较——两个不同日期的大小区分

全局

bool operator<(const Date& d1, const Date& d2)//用一个函数去比较,有别名接收
{//1.访问对象,将private注释掉if (d1._year < d2._year){return true;}else if (d1._year == d2._year && d1._month < d2._month){return true;}else if (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day){return true;}elsereturn false;
}

此处需要用到拷贝构造函数,用两个形参承载,并且进行比较(此时不能用private进行限定)

如果写入类里面呢?

注意

1.不能通过连接其他符号来创建新的操作符:比如operator@

2.重载操作符必须有一个类类型参数

3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this

5.    .      *   ::     sizeof     ?:    .这五个运算符不能重载

类内部定义运算符重载函数

bool operator<(const Date& d)//用一个函数去比较,用别名接收,参数个数为运算数-1{//1.访问对象,将private注释掉if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}elsereturn false;}

在主函数里调用

int main()
{//自定义类型怎么比较大小?——本身不支持比较,需要通过运算符重载才可以进行比较Date d1(2023, 7, 21);Date d2(2022, 8, 21);cout << (d1 < d2) << endl;cout << (operator<(d1, d2)) << endl;cout << (d1.operator<(d2)) << endl;return 0;
}

需要注意的是,左操作数是this,指向调用函数的对象

bool operator<(Date* this,const Date& d)

实现日期是否相同

bool operator<(const Date& d)//用一个函数去比较,用别名接收{//1.访问对象,将private注释掉if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}elsereturn false;}
bool operator==(const Date& d)
{return _year == d._year && _month == d._month && _day == d._day;
}

this指针的复用!!!

对于判断日期是否大于,可以通过对两个函数的复用来实现

bool operator<=(const Date& d)//此时d1作为operator,this指向d1,然后d2作为d{return *this < d || *this == d;//直接用this指针实现复用其它函数
}bool operator>=(const Date& d)
{return !(*this == d);
}

日期天数的改变

因位需要改变天数,也就是在原对象上进行数据修改,所以要拷贝构造

int GetMonthDay(int year, int month)
{int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if ((month==2)&&(year % 4 == 0 && year % 100 != 0)||(year%400==0)){return 29;}return monthArray[month];
}Date& operator+=(int day)//传值返回需要用拷贝构造,所以用引用返回
{_day += day;while (_day>GetMonthDay(_day,_month)){_day -= GetMonthDay(_day, _month);++_month;if (_month == 13){_year++;_month = 1;}}return *this;//此时要返回一个日期,日期存在于this指针里,所以要返回*this
}//出了作用域this指针仍存在,所以可以使用引用符号

日期天数的增加

由于仅进行天数的增加然后拷贝给别人,所以需要先拷贝构造一个载体tmp,然后对tmp进行修改,最后返回载体,这样子原对象的内容就得到了保证

Date operator+(int day)
{Date tmp(*this);//进行对this指针的拷贝构造,需要一个载体tmp += day; //this指向的就是d1里的数据,此时tmp就是*this,可以实现复用//用+=的运算符重载函数return tmp;
}
Date ret1 = d1 += 50;
ret1.Print();
d1.Print();//由于是+=,所以d1也会被改变Date ret=d1+50;//赋值一个日期,实际上也就是一个拷贝构造
ret.Print();
d1.Print();

实现类中成员函数的声明与定义分离

以日期类Date为例子

ps:若实现分离,那么缺省值由声明给,定义不能有缺省值

//Date.h
class Date
{
public:Date(int year = 1, int month = 1, int day = 1);//必须有缺省参数//为什么定义和声明不能分开?——因为要先找到声明再找到定义,类中会出现重定义//若想实现分开,那么就让声明给出缺省值,定义不用给void Print();int GetMonthDay(int year, int month);//连续赋值—返回指针Date& operator=(const Date& d);//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了//只读函数可以加const,内部不涉及修改生成—是否const,都可以调用//若调用了,定义里也要加上constbool operator<(const Date& d);bool operator==(const Date& d);bool operator<=(const Date& d);bool operator>=(const Date& d);bool operator!=(const Date& d);Date& operator+=(int day);//用引用返回Date operator+(int day);
private:int _year;int _month;int _day;
};//Date.cpp  定义里需要用到访问符' :: '
int Date::GetMonthDay(int year, int month)//如果用这个函数时会改变其内容,那么就需要用const修饰
{const static int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if ((month == 2) && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}return monthArray[month];
}//Date::Date(int year = 1, int month = 1, int day = 1)//err
Date::Date(int year, int month, int day)//pass
{_year = year;_month = month;_day = day;if (month < 1 || month>12 || day<1 || day>GetMonthDay(year, month)){cout << "非法日期" << endl;return;}}
//连续赋值—返回指针
Date& Date::operator=(const Date& d)//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了
{if (this != &d){this->_year = d._year;this->_month = d._month;this->_day = d._day;}return *this;//this这里指向的是d1,因为作用的对象就是d1,出了作用域this还在,所以能用引用返回//自定义类型不能传值拷贝,需要调用一个拷贝构造
}bool Date::operator<(const Date& d) //用一个函数去比较,用别名接收
{//1.访问对象,将private注释掉if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}elsereturn false;
}
bool Date::operator==(const Date& d) 
{return _year == d._year && _month == d._month && _day == d._day;
}bool Date::operator<=(const Date& d) const //此时d1作为operator,this指向d1,然后d2作为d
//再通过d1<d2 和d1==d2的判断 来得出<=的结果
{return *this < d || *this == d;//直接用this指针实现复用其它函数
}bool Date::operator>=(const Date& d)  
{return !(*this == d);
}bool Date::operator!=(const Date& d) 
{return !(*this == d);
}Date& Date::operator+=(int day)
{if (day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDay(_day, _month)){_day -= GetMonthDay(_day, _month);++_month;if (_month == 13){_year++;_month = 1;}}return *this;//此时要返回一个日期,日期存在于this指针里,所以要返回*this
}Date Date::operator+(int day) //若不想改变原内容,那么就需要一个变量来承载d1的this指针,然后改变变量,返回载体
{Date tmp(*this);//Date tmp = *this;tmp += day;return tmp;
}

赋值运算符重载

1.赋值运算符重载格式

参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

检测是否自己给自己赋值

返回*this :要复合连续赋值的含义

倘若在类中进行了拷贝不想原对象被改变,可以在定义的时候给予const进行修饰

Date (const Date& d);//拷贝构造
bool operator<(const Date& d);
bool operator<=(const Date& d);
bool operator>=(const Date& d);
bool operator>(const Date& d);

2.const重载

倘若定义对象的时候不想对象被改变,那么就会有以下的代码

void Print();void TestDate2()
{const Date d1(2023, 7, 28);//若在Print函数里就是权限的放大d1.Print();
}

但是此时Print()函数只能兼容普通类型,那怎么办?——仅需要加个关键字

void Print() const;//于此同时,定义也需要改变//void Date::Print(const Date* this)
void Date::Print() const//在结尾加上const,这样可以自由实现权限的缩小
{cout << _year <<"年" <<_month<<"月"<<_day<<"日"<< endl;
}

那么,为了代码的严谨并且保护原对象,那么对于只读函数,都可以加上const来修饰

    bool operator<(const Date& d)const;bool operator==(const Date& d)const;bool operator<=(const Date& d)const;bool operator>=(const Date& d)const;bool operator!=(const Date& d)const;

运算符赋值

我们知道有拷贝构造,那么对于运算符'=',应该也能实现两个对象之间的拷贝

void operator=(const Date& d)//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了
{//赋值拷贝//此处用不用&? —都行,为什么不会无穷递归?——此处直接传入值,不会再进行多余的拷贝构造//为了方便,默认都用引用&this->_year = d._year;this->_month = d._month;this->_day = d._day;
}

但是若是想实现多次赋值呢?例如这样的:

int  main()
{int i,j,k;k=10;i=j=k;return 0;
}

那么我们需要一个返回值,也就是需要返回一个拷贝,才能实现多次赋值

Date& operator=(const Date& d)//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了
{if (this != &d){this->_year = d._year;this->_month = d._month;this->_day = d._day;}return *this;//this这里指向的是d1,因为作用的对象就是d1,出了作用域this还在,所以能用引用返回//自定义类型不能传值拷贝,需要调用一个拷贝构造
}

赋值重载和拷贝构造的区别?

赋值,两个已经存在的对象进行拷贝
拷贝构造,一个已经存在对象,去初始化另一个要创建的对象

前置++和后置++重载

在类里,由于都有'++'操作符,所以为了区分,我们用' int '类型来区分

Date& operator++();//前置Date& Date::operator++()//改变自身,所以直接作用于this指针
{*this += 1;return *this;
}Date operator++(int);//后置Date Date::operator++(int)//仅对变量赋值作改变,所以需要用一个载体来进行数据的改变
{Date tmp(*this);*this += 1;return tmp;
}

顺序表类的实现及使用

struct SeqList
{
public:void PushBack(int x){_a[_size++] = x;}void CheckCapacity(){;}size_t size() const{return _size;}//打印void Print(){for (int i = 0; i < _size; i++){cout << _a[i] << endl;}}//读const int& operator[](size_t i) const //operator作用于类的对象{assert(i < _size);return _a[i];}//写int& operator[](size_t i)//operator作用于类的对象{assert(i < _size);return _a[i];}
private:int* _a=(int*)malloc(sizeof(int)*10);int _size=0;int _capacity=0;
};

在C语言中,我们实现一个顺序表数据的放入会及其麻烦,但是在C++中有了类,那么就可以极大的简化代码

int main()
{SeqList st;st.PushBack(1);st.PushBack(2);st.PushBack(3);st.PushBack(4);return 0;
}

访问——可以通过运算符重载实现

for (size_t i = 0; i < st.size(); i++)
{cout << st[i] << " ";//用运算符重载,这样不仅实现了特定位置数据的返回,还可以进行遍历cout << st.operator[](i) << endl;
}

通过流输出打印对象的内容

ostream& operator<<(ostream& out, const Date& d)//类的流插入
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}

流输入输入对象的内容

istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}

以上两个不做介绍,需要用到out相关类的知识

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/16118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flask处理表单数据

flask处理表单数据 处理表单数据在任何 web 应用开发中都是一个常见的需求。在 Flask 中&#xff0c;你可以使用 request 对象来获取通过 HTTP 请求发送的数据。对于 POST 请求&#xff0c;可以通过 request.form 访问表单数据。例如&#xff1a; from flask import Flask, r…

IDEA中连接虚拟机 管理Docker

IDEA中连接虚拟机 管理Docker &#x1f4d4; 千寻简笔记介绍 千寻简笔记已开源&#xff0c;Gitee与GitHub搜索chihiro-notes&#xff0c;包含笔记源文件.md&#xff0c;以及PDF版本方便阅读&#xff0c;且是用了精美主题&#xff0c;阅读体验更佳&#xff0c;如果文章对你有帮…

【点云处理教程】00计算机视觉的Open3D简介

一、说明 Open3D 是一个开源库&#xff0c;使开发人员能够处理 3D 数据。它提供了一组用于 3D 数据处理、可视化和机器学习任务的工具。该库支持各种数据格式&#xff0c;例如 .ply、.obj、.stl 和 .xyz&#xff0c;并允许用户创建自定义数据结构并在程序中访问它们。 Open3D 广…

KafKa脚本操作

所有操作位于/usr/local/kafka_2.12-3.5.1/bin。 rootubuntu2203:/usr/local/kafka_2.12-3.5.1/bin# pwd /usr/local/kafka_2.12-3.5.1/bin rootubuntu2203:/usr/local/kafka_2.12-3.5.1/bin# ls connect-distributed.sh kafka-delegation-tokens.sh kafka-mirror-mak…

15. Spring AOP 的实现原理 代理模式

目录 1. 代理模式 2. 静态代理 3. 动态代理 3.1 JDK 动态代理 3.2 CGLIB 动态代理 4. JDK 动态代理和 CGLIB 动态代理对比 5. Spring代理选择 6. Spring AOP 实现原理 6.1 织入 7. JDK 动态代理实现 8. CGLIB 动态代理实现 9. 总结 1. 代理模式 代理模式&#xf…

Mac查看系统状态

syatem profiler mac系统中提供了system profiler来查看系统的详细信息&#xff0c;包括硬件、网络以及安装的软件 Console 显示了系统上的日志文件信息&#xff0c;有助于诊断问题 Activity Monitor 可以提供正在运行的系统的相关信息 https://zhhll.icu/2021/Mac/查看系统…

【云原生】一文学会Docker存储所有特性

目录 1.Volumes 1.Volumes使用场景 2.持久将资源存放 3. 只读挂载 2.Bind mount Bind mounts使用场景 3.tmpfs mounts使用场景 4.Bind mounts和Volumes行为上的差异 5.docker file将存储内置到镜像中 6.volumes管理 1.查看存储卷 2.删除存储卷 3.查看存储卷的详细信息…

Java课题笔记~Maven基础

2、Maven 基础 2.1 Maven安装与配置 下载安装 配置&#xff1a;修改安装目录/conf/settings.xml 本地仓库&#xff1a;存放的是下载的jar包 中央仓库&#xff1a;要从哪个网站去下载jar包 - 阿里云的仓库 2.2 创建Maven项目

MySQL数据库 【索引事务】

目录 一、概念 二、索引的优缺点 1、索引的优点 2、索引的缺陷 三、索引的使用 1、查看索引 2、创建索引 3、删除索引 四、索引底层的数据结构 1、B树 2、B树 五、索引事务 1、概念和回滚 2、事务的使用 3、事务的基本特性 4、并发会遇到的问题 &#xff08…

jenkins执行jmeter时,报Begin size 1 is not equal to fixed size 5

jenkins执行jmeter脚本的时候一直提示如下错误&#xff1a; Tidying up ... Fri Jul 28 17:03:53 CST 2023 (1690535033178) Error generating the report: org.apache.jmeter.report.dashboard.GenerationException: Error while processing samples: Consumer failed wi…

游游的排列构造

示例1 输入 5 2 输出 3 1 5 2 4 示例2 输入 5 3 输出 2 1 4 3 5 #include<bits/stdc.h> using namespace std; typedef long long ll; const int N1e55; int n,k; int main(){scanf("%d%d",&n,&k);int xn-k1;int yn-k;int f1;for(int i1;i&l…

产品经理如何平衡用户体验与商业价值?

近期负责前端产品设计工作的小李忍不住抱怨&#xff1a;公司总是要求客户第一&#xff0c;实现客户良好体验&#xff0c;但在实际操作过程中&#xff0c;面向用户 体验提升的需求&#xff0c;研发资源计划几乎很难排上&#xff0c;资源都放在公司根据业务价值排序的需求…

大家做性能测试都用什么工具

在进行测试时&#xff0c;选择适合的测试工具至关重要&#xff0c;因为优秀的测试工具能够显著提高工作效率。对于性能测试和自动化测试而言&#xff0c;大多数人会选择传统的JMeter等工具&#xff0c;然而这些工具存在学习成本高、使用门槛高的问题。 因此&#xff0c;我在这…

intellij 编辑器内性能提示

介绍 IntelliJ IDEA已经出了最新版的2023.2&#xff0c;最耀眼的功能无法两个 AI Assistant编辑器内性能提示 AI Assistant 已经尝试过了是限定功能&#xff0c;因为是基于open ai,所以限定的意思是国内无法使用&#xff0c;今天我们主要介绍是编辑器内性能提示 IntelliJ Pr…

Flink回撤流

1.回撤流定义&#xff08;RetractStream&#xff09; Flink 的回撤流是指在 Flink 的流处理算法中&#xff0c;撤回已经发送到下游节点的数据。这是因为在实际应用场景中&#xff0c;有些错误数据可能会发送到下游节点&#xff0c;因此需要回撤流以保证数据的准确性。 回撤流…

EP4CE6E22C8N Error: Can‘t recognize silicon ID for device 1

经过各种排查&#xff0c;发现是AS配置不对&#xff0c;仅供参考 工程 参考某处的工程画板配置的FPGA板子&#xff0c;用于学习入门FPGA。 烧录sof文件是正常的&#xff0c;并能正常运行。 但是烧录jic是failed&#xff0c;查看报错为&#xff1a;Error: Can’t recognize si…

渗透测试:Linux提权精讲(二)之sudo方法第二期

目录 写在开头 sudo expect sudo fail2ban sudo find sudo flock sudo ftp sudo gcc sudo gdb sudo git sudo gzip/gunzip sudo iftop sudo hping3 sudo java 总结与思考 写在开头 本文在上一篇博客的基础上继续讲解渗透测试的sudo提权方法。相关内容的介绍与背…

1004. 最大连续1的个数 III

题目描述&#xff1a; 主要思路&#xff1a; 刚看到这个问题首先想到的是二分答案&#xff0c;二分长度&#xff0c;然后利用滑动窗口判断是否可以达成。 class Solution { public:bool find(int x,vector<int> nums, int k){int now0;for(int i0,j0;i<nums.size();…

根据端口号查找服务位置

已知服务的IP和端口&#xff0c;查找该服务所在位置 1、打开命令提示符&#xff08;CMD&#xff09; WINR快捷键打开运行对话框&#xff0c;输入CMD&#xff0c;打开命令行。 2、找到对应的PID或程序名称 输入netstat -ano|findstr 端口号&#xff0c;找到对应的PID&#…

解决:h5的<video>在移动端浏览器无法自动播放

并不是所有的移动端浏览器都无法自动播放&#xff0c;下载谷歌、火狐、edge等都可以正常播放&#xff0c;目前发现夸克浏览器无法自动播放。即autoplay属性失效。 <video autoplay"autoplay"></video> 可能移动端有移动端的策略&#xff0c;但解决夸克…