二,类和对象
零,先导:
- C语言是面向过程的,关注的是过程,分析出问题的步骤,通过逐步调用函数来解决问题。
对于把大象放进冰箱:
开冰箱门->放大象->关冰箱门
- 而像python和C++是面向对象的语言。
借助孙兴华老师的课件(python)再次描述这个过程:
关注的是对象,将一个事情拆分成不同的对象,靠对象之间的交互完成的。
而面向对象有如下优点:
-
易维护,易复用,易拓展
-
封装,继承,多态
面向过程适用单片机、嵌入式、驱动开发等对效率要求高的领域
面向对象适用服务器等大项目的开发。
零零,左值和右值
1.左值
-
左值是一个具体的内存位置,可以被取地址
-
左值是变量、对象、数组元素、具有名称的表达式
-
出现在等号左边,可以被赋值
2.右值
-
临时的,无法取地址的表达式
-
右值是字面常量(42),表达式的计算结果,临时对象
-
不出现在左侧,无法被赋值
一,类
1.基本形式:
要想有对象,则必须有类。
类像一个模板一样,每一个生成的对象按照类这个模板来生成。
1.1 形式1
类的定义类似于C语言中的结构体:
需要注意struct实现的类默认成员都是共有的
struct CPP
{
int add(int x, int y); //成功,C++中的struct可以看作类,而类中允许定义函数
};
//但是注意c语言中不允许函数声明
1.2 形式2(标准形式)
class也就是标准形式实现的类,默认都是私有的
class CPP
{
int add(int x, int y); //我们一般通过 class 定义类
};
引入class类主要的目的是为了将类更好的封装起来。
//类的定义形式
class ClassName
{
public:
int add(int x, int y); //成员函数 add
//……
private:
int _a; //成员变量 _a
int _b;
//……
};
会发现:变量前面加了个_表示这个变量是私有的
2.类的定义方式:
- 函数短小且被经常调用的情况:
将函数定义在类里面,函数会被编译器当作内联函数处理,提高运行速度。
- 在类中定义函数(.h)在类外实现函数(.cpp),分开定义时实现要写classname::函数名
例如:int MathUtils::add(int a, int b)
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
class MathUtils {
public:
int add(int a, int b);
};
#endif
// math_utils.cpp
#include "math_utils.h"
int MathUtils::add(int a, int b) {
return a + b;
}
3.访问限定符:
class中有三种访问限定符
-
public共有:可以在类外访问成员变量
-
protected保护,能在类中被访问,也可以在其派生的类中被访问
-
privated私有:被修饰的成员只能在类中访问
如果不加访问限定符:成员全部为私有!!!
class Limit
{
//公有
public:
int _a;
int _b;
//保护
protected:
int _c;
//私有
private:
int _d;
int _e;
int _f;
};
tip:访问限定符只有在编译的时候有用,当数据映射到内存后,没有任何访问限定符上的区别。就像这样:
#include <iostream>
class MyClass {
public:
int publicVar;
void publicMethod() {
std::cout << "Public method called." << std::endl;
}
private:
int privateVar;
void privateMethod() {
std::cout << "Private method called." << std::endl;
}
};
int main() {
MyClass obj;
// 访问public成员,编译时是允许的
obj.publicVar = 42;
obj.publicMethod();
// 访问private成员,编译时会导致错误
// obj.privateVar = 10; // 编译错误
// obj.privateMethod(); // 编译错误
return 0;
}
4.成员变量
在class中定义的普通变量,被称为成员变量,一般简称为成员
每个实例化出来的对象,都有自己的成员变量,互不冲突,为了使成员变量更加容易辨别,我们一般会在成员变量前加上_修饰
//定义一个日期类
class Date
{
private:
int _year; //年
int _month; //月
int _day; //日
//上述变量都被称为类中的成员变量,简称为 成员
};
5.成员函数
类可以在里面定义函数:称为成员函数,也被称为方法。
成员函数是公用的,因为成员函数虽然明面上写在类中。但实际上位于代码段,对象调用时是通过地址调用的函数。
#include<iostream>
using namespace std;
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;
};
6.实例化和调用
当我们拥有一个正常的类后,我们可以通过实例化的方式创建对象
int main()
{
Date d1; //实例化出对象 d1,实际d1值为 1970 1 1
return 0;
}
.操作符实现 类的方法 的调用
int main()
{
Date d1; //实例化出对象 d1,实际d1值为 1970 1 1
d1.Print(); //调用 Date 类中的 Print 方法,打印 1970年1月1日
return 0;
}
注意:类可以实例化多次,产生多个对象,这些对象的成员变量独立存在,但成员函数是公用的。
7.sizeof(Date)=12个字节:三个int
空类大小为一个字节:
编译器通常会为其分配一个字节的内存,以确保对象有一个非零的地址。这通常被称为**“空对象的占位”**,因为它确保对象在内存中有足够的空间以便可以被唯一标识。、
#include<iostream>
using namespace std;
class date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
//声明 和 定义 判断
//开空间了就是定义
//没开空间就是声明
//这里是声明
int _year;
int _month;
int _day;
};
int main()
{
// 类 和 对象就是一对多的关系
//就像设计图 和 房子 也是1vn
date d;//定义出对象,也叫做类的实例化
//当把private去掉之后
//date::_year = 1;//没有空间,不能进行赋值
d.Init(2023, 10, 17);
date d1;
date d2;
date d3;
d1.Init(2023, 10, 17);
cout << sizeof(d1) << endl;
}
8.类实现封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户方便使用类。
在C++实现封装,可以通过类将数据和操作数据的方法进行有机结合,通过访问权限来隐藏对象内部的实现细节,控制哪种方法可以在类的外部被直接使用。
二,this指针
类具有这么一个特性:
每个对象都有自己的成员变量,而成员变量需要通过对象来调用。发生调用成员函数的行为,就一定能找到对象对应的成员变量。
这原因在于c++的编译器给每个非静态成员变量函数增加了一个隐藏参数指针this指针,
该指针指向成员函数的对象,
所有设计对象的成员变量的操作,都是通过this指针调用的
(非静态成员函数是用来操作类的成员变量或执行其他与类的状态相关的操作的函数。)
所以实际上是这样的:
void Print(Date* const this) //这个参数也是编译器自动设计并接收使用的
{
cout << this->_year;
//实际使用中,下面两种形式是完全一样的,即使我们不主动通过 this 指针指向成员变量
//编译器也会自动给我们加上 this 指针,指向当前对象的成员变量
_year; //实际效果等价于 this->_year
this->_year;
}
d1.Print(&d1); //其中 &d1 这个参数传递是由编译器自动完成的,我们不能主动干预
注意:
-
this
指针是被const
修饰的,也就是说this
指针只能指向当前对象 -
this
指针只能在成员函数
的内部使用 -
this
指针不存储在对象中,是通过参数传参的形式传递给成员函数
的,这个行为是编译器自动执行的 -
this
指针是一个隐含形参,位于参数列表的第一个,一般情况下通过寄存器ecx
自动传递 -
可以存在一个指向空的对象指针 pc,通过此指针调用函数时,只要不发生
this
指针解引用情况,是不会报错的,因为此时this
指针为空指针#include
class MyClass {
public:
int data;void printData() { if (this != nullptr) { // 检查this指针是否为空 std::cout << "Data: " << data << std::endl; } else { std::cout << "Object is null." << std::endl; } }
};
int main() {
MyClass* pc = nullptr; // 创建一个指向空对象的指针pc->printData(); // 调用成员函数,不引用this指针 return 0;
}
//虽然在上述情况下没有发生this指针解引用,因此不会引发运行时错误,
//但这并不是一种良好的实践,因为操作一个空对象通常是没有意义的。
1.面试题1:this指针存在哪里
this指针没有存在哪里,它是一个特殊的指针,在函数调用的时候被创建,并指向**对象地址,**会在exc寄存器中自动被传递
2.面试题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;
}
//崩溃
//这里崩溃是因为调用函数,在printa下面this->_a会发生解引用
三,默认成员函数
1.构造函数
1.1构造函数的创建规则:
-
函数名和类名相同。
-
不需要返回值,甚至连void都不需要写。
-
对象实例化时,编译器自动调用默认构造函数
-
构造函数支持重载,可以存在多个构造函数,但默认构造函数只有一个.
1.2构造函数写法
不带参数或参数为全缺省****的构造函数 书写形式就有两种:全缺省参数和不带参数的写法
class Date
{
public:
//特别注意:默认构造函数只允许存在一个形式
//一般推荐使用形式二:全缺省参数
//因为这样方便后续初始化时指定值
//默认构造函数形式一:不带参数
//Date()
//{
// _year = 1970;
// _month = 1;
// _day = 1;
//}
//*(推荐)默认构造函数形式二:参数为全缺省
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int year = 1970, int month = 1, int day = 1)" << endl;
}
//其他普通构造函数,只要与默认构造函数构成重载,都合法
Date(double b)
{
_year = 2023;
_month = 2;
_day = 9;
cout << "Date(double b)" << endl;
}
private:
int _year;
int _month;
int _day;
};
#include<iostream>
using namespace std;
class Date
{
public:
//特别注意:默认构造函数只允许存在一个形式
//一般推荐使用形式二:全缺省参数
//因为这样方便后续初始化时指定值
//默认构造函数形式一:不带参数
//Date()
//{
// _year = 1970;
// _month = 1;
// _day = 1;
//}
//默认构造函数形式二:参数为全缺省
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date(int year = 1970, int month = 1, int day = 1)" << endl;
}
//其他普通构造函数,只要与默认构造函数构成重载,都合法
Date(double b)
{
_year = 2023;
_month = 2;
_day = 9;
cout << "Date(double b)" << endl;
}
void print()
{
cout << _year << "and" << _month << "and" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout << endl;
Date d2(1.1);//调用另一个构造函数
cout << endl;
d1.print();
return 0;
}
1.2.1默认缺点:
- 默认构造函数不对内置数据类型进行处理:如int、double、char等 语言原生提供的
(有时不确定具体要看编译器,但注意最好当成不处理)
- 像自定义类型,默认函数会去调用属于他们自己的初始化函数。
简而言之:就是编译器自动生成的默认函数什么也没干
1.2.2缺点补丁:
在成员变量声明时,将内置类型给上缺省值,这样
编译器生成的默认构造函数时,就会以这些缺省值来初始化成员变量
class Date
{
private:
//注意此时还是声明!!因为是缺省值
int _year = 2023; //在内置类型声明时给上缺省值
int _month = 2; //这样即使调用生成的默认构造函数
int _day = 9; //也能达到初始化的效果
};
1.3自己写的小例子供理解使用
//默认构造函数缺省值
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
public:
person(string sex = "female", int age = 99, string name = "Mike")
{
_sex = sex;
_age = age;
_name = name;
cout << "调用了构造函数" << endl;;
}
private:
string _sex = "male";
int _age = 18;
string _name = "John";
};
int main()
{
person p1;//当存在构造函数的时候就不会调用默认构造函数,而调用自己的构造函数
person p2("malee", 55);//p2 {_sex="malee" _age=55 _name="Mike" } 类型:person
return 0;
}
//如果不写调用构造函数,也不打补丁就不能进行初始化了
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
private:
string _sex;
int _age ;
string _name;
};
int main()
{
person p1;//调用默认构造函数,但是会发现不对内置类型进行处理//p1 {_sex="" _age=-858993460 _name="" } person
return 0;
}
//这个就是补丁了
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
public:
private:
string _sex="male";
int _age=18;
string _name="John";
};
int main()
{
person p1;
return 0;
}
2.析构函数
对象在销毁的时候会自动调用析构函数,完成对对象中资源的清理工作。
2.1特性:
-
析构函数的函数名就是在类名前加字符~
-
无参数and无返回值类型
-
析构函数不能重载(一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数)
-
当对象的生命周期结束时,C++编译系统自动调用析构函数。
#include<stdio.h>
#include<stdlib.h>
using namespace std;
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror(“malloc申请空间失败!!!”);
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();_array[_size] = data; _size++; } // 其他方法... ~Stack() { if (_array) { free(_array); _array = NULL; _capacity = 0; _size = 0; } }
private:
DataType* _array;
int _capacity;
int _size;
};int main()
{
Stack s;
s.Push(1);
s.Push(2);
return 0;
}
5.编译器生成的默认析构函数,可以调用自定义类型的析构函数
默认析构函数对自定义类型 调用 属于她的析构函数
而默认析构函数 对内置类型不进行处理
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
//结果生成~Time()
//调用date的默认析构函数的时候,调用了自定义类型Time的析构函数
来自chatgpt的解释:
2.2自己写的例子供理解参考使用
//析构函数的初次使用
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
public:
private:
string _sex = "male";
int _age = 18;
string _name = "John";
};
int main()
{
person p1;
return 0;
//我们会发现在进行到了这一步的时候,会发现string类型里面是个空字符串,而age没有改变。
//这是因为析构函数对内置类型并不进行处理
}
//默认析构函数对于自定义类型,做出调用其自定义函数的行为
#include<iostream>
#include<cstdio>
using namespace std;
class Skill
{
public:
~Skill()
{
cout << "I wanna be a lifelong learner" << endl;
}
private:
int _math;
int algorithm;
};
class person
{
public:
private:
string _sex = "male";
int _age = 18;
string _name = "John";
Skill personal_skill;//会发现调用了Skill的析构函数
};
int main()
{
person p1;
return 0;
//我们会发现在进行到了这一步的时候,会发现string类型里面是个空字符串,而age没有改变。
//这是因为析构函数对内置类型并不进行处理
}
3.拷贝构造函数
3.1为什么存在拷贝构造函数?
因为内置类型,我们可以通过赋值实现
而自定义类型,涉及的比较复杂所以拷贝构造函数就产生了。
这样会导致重复析构,造成程序崩溃。
所以正确做法:开辟空间->拷贝数据->更新指向->完成拷贝
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
public:
person(string sex = "female", int age = 99, string name = "Mike")
{
_sex = sex;
_age = age;
_name = name;
cout << "调用了构造函数" << endl;;
}
person(const person& d)//命名仍然与构造函数相同,不过参数类型为类
{
//将d拷贝给*this
_sex=d._sex;
_age = d._age;
_name = d._name;
}
//这里的底层实现是这样的
//person(person* const this,const person& d)
//{
// //第一个就是传入的自身比如person p1(p2),那么这里就是person p1(&p1,p2)
// //将d拷贝给*this
// *this->_sex = d._sex;
// *this->_age = d._age;
// *this->_name = d._name;
//}
private:
string _sex = "male";
int _age = 18;
string _name = "John";
};
int main()
{
person p1;
person p2(p1);//使用方法1
person p3 = p1;//使用方法2
return 0;
//我们会发现在进行到了这一步的时候,会发现string类型里面是个空字符串,而age没有改变。
//这是因为析构函数对内置类型并不进行处理
}
3.2拷贝构造函数好好好:对简单内置类型直接拷贝!
但是注意这个是浅拷贝
3.3默认拷贝构造函数
默认的会按照内存存储 按字节序 来完成拷贝,叫做浅拷贝。
对于涉及空间开辟的,一定要写默认拷贝构造函数
例如:
class SeqList
{
public:
//现在编写一个涉及空间开辟的拷贝构造函数
SeqList(const SeqList& tmp)
{
_pa = (int*)malloc(sizeof(int) * _capacity);
if (nullptr == _pa)
{
cout << "malloc fail" << endl;
exit(-1); //失败,直接退出程序
}
//将 tmp 空间中的值,拷贝到 *this 中
memcpy(_pa, tmp._pa, sizeof(int) * _size);
_size = tmp._size;
_capacity = tmp._capacity;
}
private:
int* _pa = nullptr;
int _size = 0;
int _capacity = 4; //动态顺序表
};
3.4 传引用 而不是 传值
值调用,需要先 生成 临时变量,再进行传递;
传引用,就可以避免产生 临时变量。
那么值调用的场景如下:会发生无限递归
3.5 使用场景
-
用已存在的对象创建新对象时
-
函数参数类型为 类 这个类型的对象时
-
函数返回值为 类 这个类型的对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << “Date(int,int,int):” << this << endl;
}
Date(const Date& d)
{
cout << “Date(const Date& d):” << this << endl;
}
~Date()
{
cout << “~Date():” << this << endl;
}private:
int _year;
int _month;
int _day;
};Date Test(Date d)//场景2
{
Date temp(d);
return temp;//场景3
}int main()
{
Date d1(2022, 1, 13);//调用构造函数
Test(d1);// 场景1return 0;
}
-
Date d1(2022, 1, 13);//调用构造函数
-
test(d1)以值传递的时候调用 拷贝构造函数 创建d
-
Date temp(d);创建temp调用 拷贝构造函数
-
return temp;返回时使用temp拷贝构造临时对象来返回
小结
-
当写了拷贝构造函数的时候(完成复杂对象的拷贝),证明需要析构函数来释放对象。
-
只写拷贝,不些构造,编译器认为拷贝是构造,会报错
所以拷贝构造函数存在的前提是构造函数函数****存在
4.运算符重载
运算符重载是具有特殊函数名字的函数
语法:返回值类型 operator操作符(参数列表)
4.1 使用注意事项(选看):
4.2 详述operator操作符
operator使用规则:
-
operator函数的操作数 取决于参数个数
-
operator一般写在类中,方便用this指针访问成员变量
-
this指针算作一个隐藏参数
使用例子:
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
public:
person(string sex = "female", int age = 99, string name = "Mike")
{
_sex = sex;
_age = age;
_name = name;
cout << "调用了构造函数" << endl;;
}
person(const person& d)//命名仍然与构造函数相同,不过参数类型为类
{
//将d拷贝给*this
_sex=d._sex;
_age = d._age;
_name = d._name;
}
bool operator==(const person& d1)
{
return _sex == d1._sex && _age == d1._age && _name == d1._name;
}
private:
string _sex = "male";
int _age = 18;
string _name = "John";
};
int main()
{
person p1;
person p2;
cout << (p1 == p2) << endl;
return 0;
}
bool operator==(const person& d1)
{
return _sex == d1._sex && _age == d1._age && _name == d1._name;
}
4.3 赋值重载函数=
当一个对象没有实例化时用拷贝构造
两个对象都已经存在,使用赋值重载
1.使用:将d1对象赋值给d2,非拷贝构造。
person& operator=(const person& d)
{
//能引用的地方全部采用引用,避免拷贝构造的调用
if (&d == this)
{
return *this;
}
//判断相同来避免资源浪费
_age = d._age;
_sex = d._sex;
_name = d._name;
return *this;
}
如果不使用引用这个形参 和 引用这个返回值
将会造成如下后果:
改回了上面代码的引用将不会出现问题:
2.默认赋值重载还是按照字节序的浅赋值
所以一旦涉及动态内存开辟,必须自己实现深度拷贝
3.修饰this指针const(权限平移交接的效果)
两个方法:
1.在函数后写上const:
表示*this不会被修改,即this指针指向内容不被修改
void print()const
{
cout<< _age <<endl;
cout << _sex << endl;
cout << _name << endl;
}
2.在实例化对象的时候,在对象的实例前加上const
只能调用const修饰的函数(被称为常量函数的函数)
但会发现仍可以调用构造函数、析构函数、拷贝构造函数
4.4 前置++ 和 后置++
person& operator++()//前置加加
{
_age += 1;
return *this;
}
person& operator++(int)//后置加加
{
person temp(*this);
_age += 1;
return temp;
}
5.取地址重载函数
person* operator&()//这是个person类型的指针
{
return this;//返回地址
}
6.const成员以及const取地址操作符重载
const person* operator&()const//这是个person类型的指针
{
return this;//返回地址
}
四,身份认证的实现
#include<iostream>
#include<cstdio>
using namespace std;
class person
{
private:
string _sex = "male";
int _age = 18;
string _name = "John";
public:
person(string sex = "female", int age = 99, string name = "Mike")
{
_sex = sex;
_age = age;
_name = name;
cout << "调用了构造函数" << endl;;
}
person(const person& d)//命名仍然与构造函数相同,不过参数类型为类
{
//将d拷贝给*this
_sex=d._sex;
_age = d._age;
_name = d._name;
cout << "调用了拷贝构造函数" << endl;
}
//拷贝构造函数的底层实现:
//person(person* const this,const person& d)
//{
// //第一个就是传入的自身比如person p1(p2),那么这里就是person p1(&p1,p2)
// //将d拷贝给*this
// *this->_sex = d._sex;
// *this->_age = d._age;
// *this->_name = d._name;
//}
~person()
{
cout << "调用了析构函数" << endl;
}
person* operator&()//这是个person类型的指针
{
return this;//返回地址
}
bool operator==(const person& d1)
{
//能用引用的地方就引用避免拷贝构造
return _sex == d1._sex && _age == d1._age && _name == d1._name;
}
person& operator=(const person& d)
{
//能引用的地方全部采用引用,避免拷贝构造的调用
if (&d == this)
{
return *this;
}
_age = d._age;
_sex = d._sex;
_name = d._name;
return *this;
}
void print()const
{
cout<< _age <<endl;
cout << _sex << endl;
cout << _name << endl;
}
void daodanhanshu()
{
_name = _sex;
}
person& operator++()//前置加加
{
_age += 1;
return *this;
}
person& operator++(int)//后置加加
{
person temp(*this);
_age += 1;
return temp;
}
};
五,日期类的实现
#include<iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
int GetMonthDay(int year, int month)
{
static int monthdayarr[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;
}
else
{
return monthdayarr[month];
}
}
//构造函数
Date(int year = 1949, int month = 10, int day = 1)
{
_year = year;
_month = month;
_day = day;
//检查日期的合法性
if (!(year >= 1 && (month >= 1 && month <= 12) && (day >= 1 && day <= GetMonthDay(year, month))))
{
cout << "非法日期" << endl;
}
cout << "调用了构造函数哦" << endl;
}
//析构函数
~Date()
{
_year = 0;
_month = 0;
_day = 0;
cout << "调用了萌萌的析构函数来销毁哦" << endl;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
bool operator==(const Date& d)
{
return _year = d._year && _month == d._month && _day == d._day;
}
bool operator>(const Date& d)
{
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;
}
return false;
}
//现在有了>和==会发现>= <= <都是由>和==来逻辑运算得到的
bool operator>=(const Date& d)
{
return *this > d || *this == d;
}
bool operator<=(const Date& d)
{
return !(*this > d);
}
bool operator<(const Date& d)
{
return !(*this >= d);
}
//+= + - -=
Date& operator+=(int day)
{
if (day < 0)
{
_day = _day - abs(day);
return *this;
}
_day = _day + day;
while (_day > GetMonthDay(_year, _month))
{
_day = _day - GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
Date& operator-=(int day)
{
if (day < 0)
{
return *this += abs(day);
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//d2=d1+100
Date operator+(int day)
{
Date ret(*this);
ret += day;
return ret;
}
//d2=d1-100
Date operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
//前置
Date& operator++()
{
*this += 1;
return *this;
}
//后置(多个int参数为了和前置区分)
Date& operator++(int day)
{
Date tmp(*this);
*this += 1;
return tmp;
}
};
六,细节提升
1.关于初始化列表
1.为什么出现初始化列表?
因为会发现引用和const修饰的对象,传到构造函数的时候会报错啊!
这是因为在实例化一个对象进行赋值之前,已经被初始化成了随机值,而const只能初始化一次
所以会发现原来赋值初始化方式的缺点是:
-
无法给const修饰成员初始化
-
无法给引用成员初始化
-
无法给自定义成员初始化(且该类没有默认构造函数的时候)
2.初始化列表的使用
:开始 ,分隔 ()写上初始值
person(string sex = "female", int age = 99, string name = "Mike", const string address="beijing", const int& power = 10)
:_sex(sex)
, _age(age)
, _name(name)
, _address(address)
, _power(power)
{
cout << "调用了构造函数" << endl;;
}
2.explicit关键字
1.思想导入:隐式类型转换
实际上它创建了int tmp
double b=3.1415926535
int tmp=3
int a=3
2.在类中的情况?
class A
{
public:
//默认构造函数
A(int a = 0)
:_a(a)
{
//表示默认构造函数被调用过
cout << "构造函数被调用了" << endl;
}
//默认析构函数
~A()
{
_a = 0;
//表示默认析构函数已被调用
cout << "析构函数调用了,摧毁摧毁,我最棒!" << endl;
}
//拷贝构造函数
A(const A& a)
{
_a = a._a;
cout << "拷贝构造函数调用调调" << endl;
}
//赋值重载函数
A& operator=(const A& a)
{
if(this != &a)
{
_a = a._a;
}
cout << "A& operator=(const A& a)" << endl;
return *this;
}
private:
int _a;
};
使用:
int main()
{
A aa1 = 100; //注:此时整型 100 能赋给自定义类型
return 0;
}
会发现可以运行,这是为什么呢?
-
因为类中只有一个整形成员
-
赋值时,先生成同类型临时变量,即调用一次构造函数
-
再调用拷贝构造函数,将临时变量的值拷贝给aal;
这个过程:
-
也就是先 生成 了临时变量是和结构体的成员相同的类型的临时变量
-
调用拷贝构造函数,将临时变量赋值给类的成员变量
但是实际上没有调用拷贝构造函数:这是因为编译器发生了优化
所以你写的:A aa1 = 100;
实际上:A aa1(100);
编译器对于构造临时变量+拷贝构造处理的措施:直接构造。
这就是类的隐式转换优化
虽然方便,但这影响了可读性,这就引出来了explicit修饰
3.explicit关键字限制转换
在构造函数前加上explicit修饰
explicit A(int a = 0)
:_a(a)
{
//表示默认构造函数被调用过
cout << "构造函数被调用了" << endl;
}
为了提高代码的可读性和规范性要加上explicit
3.static修饰
静态修饰将变量的生命周期延长至整个程序运行的周期
static修饰的时候,只能被初始化一次。
1.在类中
被static修饰的成员被称为静态成员变量或静态成员函数
注意:
静态成员变量必须在类外初始化(定义)
静态成员函数失去了this指针,不能放为非静态变量,但为pubilc时,可以通过类名::函数名直接访问
class Test
{
public:
//Test(int val = 0, static int sVal = 0)
// :_val(val)
// , _sVal(sVal) //非法的,初始化列表也无法初始化静态成员变量
//{}
static void Print()
{
//cout << _val << endl; //非法的,没有 this 指针,无法访问对象成员
cout << _sVal << endl;
}
private:
int _val;
static int _sVal; //静态成员变量
};
int Test::_sVal = 0; //静态成员变量必须在类外初始化(定义),需指定属于哪个类
2.作用(感觉没什么用啊)
class Test
{
public:
Test(int val = 0)
:_val(val)
{
_sVal++; //利用静态成员变量进行累加统计
}
static void Print()
{
cout << _sVal;
}
private:
int _val = 0;
static int _sVal; //静态成员变量
};
int Test::_sVal = 0; //静态成员变量必须在类外初始化(定义)
int main()
{
Test T[10]; //调用十次构造函数
//通过静态成员变量验证
cout << "程序共调用了";
Test::Print();
cout << "次成员函数" << endl;
return 0;
}
4.匿名对象
生命周期短,一行结束就会销毁
用于优化性能
Date(2023, 2, 10); //匿名对象1 初始化
Date().Print(); //匿名对象2 调用打印函数
//注意:两个匿名对象相互独立,创建 匿名对象2 时, 匿名对象1 已被销毁
5.友元函数
类外的函数无法随意访问类中的私有成员
类外的可以访问私有成员的函数称为友元函数friend
class Test
{
public:
//声明外部函数 Print 为友元函数
friend void Print(const Test&d);
Test(int val = 100)
:_val(val)
{}
private:
int _val;
};
void Print(const Test& d)
{
cout << "For Friend " << d._val << endl;
}
int main()
{
Test t;
Print(t);
}
友元类:我是个类,我声明了他是朋友,他就可以访问我
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
6.编译器优化问题
1.参数优化
class A
{
public:
//默认构造函数
A(int a = 0)
:_a(a)
{
//表示默认构造函数被调用过
cout << "A(int a = 0)" << endl;
}
//默认析构函数
~A()
{
_a = 0;
//表示默认析构函数已被调用
cout << "~A" << endl;
}
//拷贝构造函数
A(const A& a)
{
_a = a._a;
cout << "A(const A& a)" << endl;
}
//赋值重载函数
A& operator=(const A& a)
{
if(this != &a)
{
_a = a._a;
}
cout << "A& operator=(const A& a)" << endl;
return *this;
}
private:
int _a;
};
构造(隐式转换)
-> 拷贝构造(传参)
-> 构造(创建aa接收参数)
构造(直接把aa构造为目标值)
2.返回优化
构造(匿名对象的创建)
-> 构造(临时变量)
-> 拷贝构造(将匿名对象拷贝给临时变量)
-> 拷贝构造(将临时变量拷贝给 a)
构造(直接把函数匿名对象值看作目标值,构造除出 a)
3.优化综述
拷贝构造+构造会优化
传值返回的时候,涉及多次拷贝构造,会优化
code技巧:
1.引用传参,不涉及拷贝构造
2.匿名构造,加速优化
3.接收参数,先定义再接收效率会降低
void myFunction(int x) {
int y = x; // 首先定义局部变量 y,然后将参数 x 的值赋给它
// 这里使用 y 进行操作
}
4.参数尽量使用const&
参考资料:
菜鸟教程 - 学的不仅是技术,更是梦想! (runoob.com)
b站孙兴华视频教程
bit c++