【C++类和对象中:解锁面向对象编程的奇妙世界】

【本节目标】

  • 1. 类的6个默认成员函数

  • 2. 构造函数

  • 3. 析构函数

  • 4. 拷贝构造函数

  • 5. 赋值运算符重载

  • 6. const成员函数

  • 7. 取地址及const取地址操作符重载

1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date {};

2. 构造函数

2.1 概念

对于以下Date类:

#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 << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象

其特征如下:

  • 1. 函数名与类名相同。
  • 2. 无返回值。
  • 3. 对象实例化时编译器自动调用对应的构造函数。
  • 4. 构造函数可以重载。
class Date
{
public:// 1.无参构造函数Date(){_year = 1;_month = 1;_day = 1;}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}// 3.重载写法:但是和上面的无参调用存在歧义,不能和1写法同时存在Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};void TestDate()
{Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();
}

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。

class Date
{
public:/*// 如果用户显式定义了构造函数,编译器将不再生成Date(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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;
}

6. 关于编译器生成的默认成员函数,很多会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

        解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在0.类中声明时可以给默认值。

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)// C++11支持,但是此时仍然是声明,此时没有对象// 声明时给了缺省值int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

总结:默认生成的构造函数,会处理自定义类型去调用其他的默认构造,而不处理内置类型。

注:Data* p也是内置类型。

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。可以不传参数就调用构造,都可叫默认构造。这三个函数只能存在一个,否则产生歧义。

class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1;//此时无参,重载与无参出现歧义
}

编译不通过:

总结:

  • 1.一般情况下,尽量自己写构造函数。
  • 2.成员内部都是自定义类型,或者声明给了缺省值,此时可以让编译器自己生成构造函数。
  • 3.内置类型都当作未处理,都是随机值。

3.析构函数

3.1 概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

3.2 特性

析构函数是特殊的成员函数,其特征如下:

  • 1. 析构函数名是在类名前加上字符 ~。
  • 2. 无参数无返回值类型。
  • 3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  • 4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
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++;}// 其他方法...//相当于destroy函数~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
void TestStack()
{Stack s;s.Push(1);s.Push(2);
}

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;
}

6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4. 拷贝构造函数

4.1 概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

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

4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  • 1. 拷贝构造函数是构造函数的一个重载形式。
  • 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日";}
private:int _year;int _month;int _day;
};// 浅拷贝
// 这里可以想象结构体传参,d1传d,传值拷贝
// 这里参数可以写出指针或者引用
void func(Date d)
{d.Print();
}int main()
{Date d1;func(d1);return 0;
}

运行结果:

上面的日期类传值拷贝没有问题,那能说明我们上面的特征结论时错误的嘛?我们来看一下栈类的情况。

#include <iostream>
using namespace std;typedef int DataType;
class Stack
{
public:Stack(int capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Print(){//...}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};//栈这里也传值拷贝,可以吗???
void  func(Stack st)
{st.Print();
}
int main()
{Stack st1;func(st1);return 0;
}

这里我们就崩溃了,为什么???

这是因为栈类除了单纯存储了普通数据_top和_capacity,还存储了一个指针_array,而C++有析构函数,传值拷贝后,指针就都指向了同一块空间,当st出了作用域,st对象就会调用析构函数去释放指针所指向的空间,然后st1又会再一次调用一次析构函数,此时就是对空指针进行释放,所以程序就报错了,所以C++传值拷贝是有风险的。而上面的日期类仅仅存储普通数据,所以上面的Date类没有问题,所以浅拷贝/值拷贝是有问题的,解决Stack类需要用到我们的深拷贝。

规定:自定义类型的对象拷贝的时候,调用一个函数,这个函数就叫做拷贝构造。

我们首先来看一下为什么拷贝构造函数参数必须类类型对象的引用,而不能是传值。

调用这个函数的时候,d1作为参数先要传参,d1去调用了拷贝构造函数dd,此时dd作为参数又要传参,dd又要去调用拷贝函数构造......只要传入的参数是对象,参数本身就会自动调用拷贝构造函数。这样就是一个无穷递归。

调用拷贝 -> 先传参(传值传参) -> 形成新的拷贝构造函数 -> ......

所以这里我们就需要传引用/传地址解决。

#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(Date& dd){//这里依然是浅拷贝_year = dd._year;_month = dd._month;_day = dd._day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日";}
private:int _year;int _month;int _day;
};// 浅拷贝
// 这里可以想象结构体传参,d1传d,传值拷贝
// 这里参数可以写出指针或者引用
void func(Date d)
{d.Print();
}int main()
{Date d1;func(d1);cout << "\n";Date d2(d1);func(d2);return 0;
}

C++规定:自定义类型对象传参拷贝时,必须调用拷贝构造。

那我们的栈类还能这样写吗?

Stack(Stack& stt)
{_array = stt._array; _capacity = stt._capacity;_size = stt._size;
}

上面这种方法仍然是错误的,是浅拷贝,同样会调用两次析构函数。这里需要用到我们的深拷贝,思想是开辟一个资源一样大的空间,然后在把值拷贝过来。

#include <iostream>
using namespace std;typedef int DataType;
class Stack
{
public:Stack(int capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}//Stack(Stack& s)//{//	下面这种方法仍然是错误的,是浅拷贝//	_array = s._array; //	_capacity = s._capacity;//	_size = s._size;//}Stack(Stack& stt){_array = (int*)malloc(sizeof(int) * stt._capacity);if (_array == nullptr){perror("malloc fail");exit(-1);}memcpy(_array, stt._array, sizeof(int) * stt._size);_size = stt._size;_capacity = stt._capacity;}void Print(){//...}~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};void  func(Stack st)
{st.Print();
}
int main()
{Stack st1;func(st1);Stack st2(st1);//构造一个st2对象,用st1初始化func(st2);return 0;
}

通过监视窗口我们可以看到st1对象和st2对象指向的是不同的空间,但是内容都是一样的,此时调用的析构函数释放的空间就是不用的。

不知道我们有没有发现,上面的Date类就算没有拷贝构造函数,也是能正常运行的,这是因为拷贝构造函数时一个默认成员函数,对内置类型会完成值拷贝,而对自定义类型Stack需要调用它的拷贝构造函数,需要字节写拷贝函数。但是并不是所有的自定义类型都要写拷贝构造函数,比如下面的MyQueue类:

#include <iostream>
using namespace std;typedef int DataType;
class Stack
{
public:Stack(int capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}//Stack(Stack& s)//{//	下面这种方法仍然是错误的,是浅拷贝//	_array = s._array; //	_capacity = s._capacity;//	_size = s._size;//}Stack(Stack& stt){_array = (int*)malloc(sizeof(int) * stt._capacity);if (_array == nullptr){perror("malloc fail");exit(-1);}memcpy(_array, stt._array, sizeof(int) * stt._size);_size = stt._size;_capacity = stt._capacity;}void Print(){//...}~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
class MyQueue
{Stack _pushst;Stack _popst;int _size;
};
int main()
{MyQueue q1;MyQueue q2(q1);return 0;
}

运行结果:

这里也完成了我们的深拷贝。Date和MyQueue默认生成的拷贝构造函数就i可以使用,对应内置类型成员完成值拷贝,对应自定义类型成员调用这个成员的拷贝构造。但是Stack类需要字节写拷贝构造函数,完成深拷贝。像我们的顺序表、链表和二叉树都需要深拷贝。

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

#include <iostream>
using namespace std;class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}//为防止写反,这里加constTime(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const 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 d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

#include <iostream>
using namespace std;
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

5. 拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
#include <iostream>
using namespace std;
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)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。

5.赋值运算符重载

5.1 运算符重载

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 10, 24);int x = 1, y = 2;bool ret1 = x > y;bool ret2 = x == y;//内置类型对象可以直接用各种运算符,内置类型都是简单类型//那自定义类型呢?//这里可以想象一下结构体的比较d1 == d2;//errord1 > d2;//errorreturn 0;
}

运行结果:

这里需要想象一下结构体的比较,比较结构体是比较内部的成员,这里的类也是类似的。

#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
//private:int _year;int _month;int _day;
};
bool Geater(Date x1, Date x2)
{if (x1._year > x2._year)return true;else if (x1._year == x2._year && x1._month > x2._month)return true;else if (x1._year == x2._year && x1._month == x2._month && x1._day > x2._day)return true;elsereturn false;
}
bool Equal(Date x1, Date x2)
{//成员私有不可访问//我们可以改一下上面的限制符privatereturn x1._year == x2._year&& x1._month == x2._month&& x1._day == x2._day;
}
int main()
{Date d1;Date d2(2023, 10, 24);cout << Geater(d1, d2) << endl;cout << Equal(d1, d2) << endl;return 0;
}

运行结果:

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

函数名字为:关键字operator后面接需要重载的运算符符号。

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

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能通过重载运算法改变其内置类型的运算规则含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* ::(域作用限定符) sizeof ?:(三目运算符) . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};
//这里会调用拷贝构造,但其实没必要
//我们直接取别名即可
bool operator>(const Date& x1, const Date& x2)
{if (x1._year > x2._year)return true;else if (x1._year == x2._year && x1._month > x2._month)return true;else if (x1._year == x2._year && x1._month == x2._month && x1._day > x2._day)return true;elsereturn false;
}
//这里会调用拷贝构造
bool operator==(const Date& x1, const Date& x2)
{return x1._year == x2._year&& x1._month == x2._month&& x1._day == x2._day;
}
int main()
{Date d1;Date d2(2023, 10, 24);cout << operator>(d1, d2) << endl;cout << operator==(d1, d2) << endl;//这里就可以直接写成内置类型比较的形式//由于流插入 << 优先级比 > 和 == 优先级高,所以这里需要加括号cout << (d1 > d2) << endl; //operator>(d1,d2)cout << (d1 == d2) << endl; //operator==(d1,d2)return 0;
}

这里需要分清运算符重载和函数重载之间的关系,它们之间没有任何关系

  • 运算符重载:自定义类型可以直接使用运算符
  • 函数重载:可以允许参数不同的同名函数

但是这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证? 这里其实可以将函数移入到Date类中。

但是这里报错了为什么呢?编译器提示:error C2804: 二进制“operator >”的参数太多,因为类中含有一个隐藏的this参数

#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//这里隐藏了一个this参数// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator>(const Date& x2){if (_year > x2._year)return true;else if (_year == x2._year && _month > x2._month)return true;else if (_year == x2._year && _month == x2._month && _day > x2._day)return true;elsereturn false;}//这里会调用拷贝构造bool operator==(const Date& x2){return _year == x2._year&& _month == x2._month                                && _day == x2._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 10, 24);cout << (d1 > d2) << endl; //d1.operator>(d2) ==> d1.operator>(&d1,d2)cout << (d1 == d2) << endl; //d1.operator==(d2) ==> d1.operator==(&d1,d2)return 0;
}

我们再来实现一下+运算符重载

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int GetMonthDay(int year, int month){assert(year >= 1 && month >= 1 && month <= 12);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];}//d1 + 100void operator+(int day){_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_month = 1;_year++;}}return;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 10, 24);d2 + 50;return 0;
}

通过监视窗口观察我们实际上实现的是+=运算符重载,因为d2对象的内容被改变了,而+运算符重载不会改变对象的内容。

d2 += 50;//这里不支持连续+=,因为日期不能+=日期

要实现连等,我们需要接收返回值,所以上面的+=运算符重载是需要返回值的。

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int GetMonthDay(int year, int month){assert(year >= 1 && month >= 1 && month <= 12);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];}//d1 += 100Date operator+=(int day){_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_month = 1;_year++;}}return *this;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 10, 22);d2 += 50;//这里不支持连续+=int i = 0;// + 操作符有返回值int ret1 = i + 50;int j = 0;// += 操作符有返回值int ret2 = j += i += 50;return 0;
}

上面我们返回的*this仍然是值拷贝,但是*this就是d2,d2的生命周期是在main函数中的,所以上面的运算符重载的返回值可以使用引用。

Date& operator+=(int day){_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_month = 1;_year++;}}return *this;}

现在我们再来写我们的+运算符重载,首先我们要知道+和+=的区别,+操作符是不改变自身的。

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int GetMonthDay(int year, int month){assert(year >= 1 && month >= 1 && month <= 12);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];}// d2 + 50Date& operator+(int day){Date tmp(*this);//拷贝一份d1tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._month = 1;tmp._year++;}}return tmp;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 10, 22);Date ret = d2 + 50;return 0;
}

这样就实现了我们的+运算符重载对象之间互不影响。

5.2 赋值运算符重载

1. 赋值运算符重载格式

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// d1 = d3void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();Date d2(2023, 10, 26);//一个已经存在的对象去拷贝初始化另一个对象Date d3(d2);//拷贝构造函数//两个已经存在的对象的拷贝d1 = d3;//赋值运算符重载d1.Print();
}

运行结果:

如果我们想要连续赋值呢?很明显连等是错误的。因为我们上面写的函数是没有返回值的。

#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// d1 = d3Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();Date d2(2023, 10, 26);//一个已经存在的对象去拷贝初始化另一个对象Date d3(d2);//拷贝构造函数//两个已经存在的对象的拷贝d1 = d3;//赋值运算符重载d1.Print();//连续赋值d2 = d1 = d3;
}

这样就实现了连等的操作。

但是我们能对象给对象自己赋值,很明显是可以的。但是按照我们上面的代码太复杂,自己赋值自己就没必须再逐一拷贝。

Date& operator=(const Date& d)//d是d1对象的别名,共用一块空间
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值 运算符重载只能是类的成员函数。

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注 意:内置类型成员变量是直接赋值的,完成值拷贝,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实 现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

#include <iostream>
using namespace std;
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必 须要实现。

现在我们再来完善一下我们的日期类。当我们的输入的日期非法程序依然能够打印出来。

注:当构造函数在声明和定义出现全缺省参数时,规定在声明的地方写全缺省参数,定义的地方不写。

Date(int year = 1900, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;if (_year < 1 || _month < 1 || _month > 12|| _day < 1 || _day > GetMonthDay(_year, _month)){assert(false);}
}

我们上面的运算符重载写了>运算符重载、=运算符重载和+=运算符重载。我们在来写一下!运算符重载等其他比较运算符重载,可以采用复用。

bool operator>(const Date& x2)
{if (_year > x2._year)return true;else if (_year == x2._year && _month > x2._month)return true;else if (_year == x2._year && _month == x2._month && _day > x2._day)return true;elsereturn false;
}
bool operator==(const Date& x2)
{return _year == x2._year&& _month == x2._month&& _day == x2._day;
}
bool operator!=(const Date& x2)
{return !(*this == x2);
}
bool operator>=(const Date& x2)
{return *this > x2 || *this == x2;
}
bool operator<(const Date& x2)
{return !(*this >= x2);
}

我们再来看一下下面两种复用哪种更优?下面这个是+操作符重载复用+=操作符重载

Date operator+=(int day)
{//如果传入的day是负数if(day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_month = 1;_year++;}}return *this;
}
Date operator+(int day)
{Date tmp(*this);tmp += day;return tmp;
}

这种是+=操作符重载复用+操作符重载的实现

Date& operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
Date operator+(int day)
{Date tmp(*this);//拷贝一份d1tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._month = 1;tmp._year++;}}return tmp;
}
Date& operator+=(int day)
{*this = *this + day;return *this;
}

上面的第一种写法+操作符重载复用+=操作符重载,+操作符重载进行了两次拷贝对象,而后面的那种写法+=操作符重载复用+操作符重载,+操作符重载进行了两次拷贝对象,+=操作符重载进行了两次拷贝对象,还有=操作符重载的一次。

我们来实现一下-=操作符重载和-操作符重载,这里仍然是让-操作符重载复用-=操作符重载

Date& operator-= (int day)
{//如果传入的day是负数if(day < 0){return *this += (-day);}    _day -= day;while (_day <= 0){_month--;if (_month == 0){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}
Date operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;
}

结果验证:

5.3 前置++和后置++重载

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){*this += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器\自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存\一份,然后给this + 1//       而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);*this += 1;return temp;}Date& operator--(){*this -= 1;return *this;}Date operator--(int){Date temp(*this);*this -= 1;return temp;}
private:int _year;int _month;int _day;
};
int main()
{Date d;Date d1(2023, 10, 27);d = d1++;    // d: 2023,10,27   d1:2023,10,28d = ++d1;    // d: 2023,10,29   d1:2023,10,29return 0;
}

5.4 日期相减重载

// d1 - d2
Date operator-(const Date& d)
{//假设左大右小int flag = 1;Date max = *this;Date min = d;//假设错了,左小右大,此时相减就是负数if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}

我们再来回顾一下我们上面的Print函数,每次该成员函数都要我们自己去实现,比较麻烦,我们可以通过运算符重载去实现打印,由于此时是自定义类型,所以可以通过重载流插入运算符实现。

cout是ostream类型的对象,cin是istream类型的对象​​​​​​​

内置类型可以支持流插入操作,是因为库里面已经实现过了。

#include <iostream>
using namespace std;class Date
{
public:// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void operator << (ostream& out)//cout是ostream的对象{out << _year << "年" << _month << "月" << _day << "日";}private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 11, 5);cout << d1;//报错,why??d1 << cout;//运行成功!!return 0;
}

运行结果:

双操作数的运算符,第一个参数是左操作数,第二个操作数是右操作数,作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this,所以对于第二种写法,第一个参数是d1,第二个参数是cout,可以转化为d1.operator<<(&d1,cout),对于第一种写法参数就不匹配,所以报错。虽然第二种写法是正确的,但是看着不习惯,要想第一种此恶法正确,我们必须第一个参数设置为cout。因此对与该函数我们可以写成全局函数。

#include <iostream>
using namespace std;class Date
{
public:// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private://此时必须注释,否则全局函数访问不到该变量int _year;int _month;int _day;
};void operator << (ostream& out,Date &d)//cout是ostream的对象
{out << d._year << "年" << d._month << "月" << d._day << "日";
}int main()
{Date d1(2023, 11, 5);cout << d1;//d1 << cout;return 0;
}

​​​​​​此时就转化成了operator<<(cout,d1),虽然上面我们确实通过将该函数放到全局变量中实现了我们的流插入操作符函数,但是使用该函数的时候我们必须要将类的成员变量设置公开,那么这样不就与封装性相悖吗?我们可以通过友元来解决。

#include <iostream>
using namespace std;class Date
{
public:// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//友元函数friend void operator << (ostream& out,Date &d);//cout是ostream的对象private:int _year;int _month;int _day;
};void operator << (ostream& out,Date &d)//cout是ostream的对象
{out << d._year << "年" << d._month << "月" << d._day << "日";
}int main()
{Date d1(2023, 11, 5);cout << d1;//d1 << cout;return 0;
}

上面使用了友元函数,这里介绍一下,友元函数就是相当你有一个私人游泳池(相当于类的私有成员变量),然后你的非常要好朋友可以直接去你的私人游泳池游泳(访问私有变量),此时你不介意。我们的流插入操作符还可以支持连续使用,想要连续使用就必须设置一个返回值,所以我们要修改一下上面的代码

#include <iostream>
using namespace std;class Date
{
public:// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//友元函数friend ostream& operator << (ostream& out,const Date &d);//cout是ostream的对象private:int _year;int _month;int _day;
};//流插入的内容d不会改变,可以加上const修饰
ostream& operator << (ostream& out,const Date &d)//cout是ostream的对象
{out << d._year << "年" << d._month << "月" << d._day << "日";return out;
}int main()
{Date d1(2023, 11, 5);Date d2;//赋值运算符顺序:从右往左//流插入运算符顺序:从左往右//由运算符的结合性决定//这里的返回值必须是coutcout << d1 << d2;return 0;
}

我们再来实现一下流提取运算符重载

#include <iostream>
using namespace std;class Date
{
public:// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//友元函数friend ostream& operator << (ostream& out,const Date &d);//cout是ostream的对象friend istream& operator >> (istream& in,Date &d);//cin是istream的对象private:int _year;int _month;int _day;
};//流插入的内容d不会改变,可以加上const修饰
ostream& operator << (ostream& out,const Date &d)//cout是ostream的对象
{out << d._year << "年" << d._month << "月" << d._day << "日";return out;
}//流插入的内容d会改变,不可以加上const修饰
istream& operator >> (istream& in,Date &d)//cin是istream的对象
{in >> d._year >> d._month >> d._day;return in;
}int main()
{Date d1(2023, 11, 5);Date d2;//赋值运算符顺序:从右往左//流插入运算符顺序:从左往右//由运算符的结合性决定//这里的返回值必须是coutcin >> d1 >> d2;cout << d1 << d2;return 0;
}

运行结果:

总结:其他的运算符一般实现成成员函数,流插入和流提取操作符必须实现到全局中,这样才能让流对象作第一个参数。流本质是为了解决自定义类型输入和输出问题,C语言的printf函数只能指定内置类型,同时打印输出的时候还要指定输出格式,C语言的printf函数和scanf函数无法解决自定义类型的输入输出问题。C++通过面向对象和运算符重载来解决。

6.日期类的实现

class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month){static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };int day = days[month];if (month == 2&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){day += 1;}return day;}// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1);// 拷贝构造函数// d2(d1)Date(const Date& d);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d);// 析构函数~Date();// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day);// 日期-天数Date operator-(int day);// 日期-=天数Date& operator-=(int day);// 前置++Date& operator++();// 后置++Date operator++(int);// 后置--Date operator--(int);// 前置--Date& operator--();// >运算符重载bool operator>(const Date& d);// ==运算符重载bool operator==(const Date& d);// >=运算符重载bool operator >= (const Date& d);// <运算符重载bool operator < (const Date& d);// <=运算符重载bool operator <= (const Date& d);// !=运算符重载bool operator != (const Date& d);// 日期-日期 返回天数int operator-(const Date& d);
private:int _year;int _month;int _day;
};

7.const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

我们来看看下面的代码

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();
}
int main()
{Test();return 0;
}

当我们给对象加上const限制时,这里报错了为什么呢?

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this,这个隐藏的this类型的Date* cosnt this,而我们传入的类型时const Date* this。这里就会存在权限放大的问题,我们需要在隐藏的this类型的前面加上cosnt,但是this类型是隐藏的,我们应该怎么加呢?C++规定在函数之后写上cosnt就可以对隐藏的this类型的前面加上cosnt。

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print() const{cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();
}
int main()
{Test();return 0;
}

加上之后非const类型对象也能运行,因为只是进行了权限的缩小。运行结果:

总结:类似于比较运算符,打印函数和+-运算符,不会修改任何成员,所以在写该类函数的时候我们可以加上const。成员函数定义的原则:1、能定义成cosnt的成员函数都应该定义成cosnt,这样const对象(权限平移)和非const对象(权限缩小)都可以调用。2、要修改成员变量的成员函数,不能定义成cosnt。3、流插入和流提取不能加上cosnt,它们不是成员函数,没有this指针,是全局函数。

请思考下面的几个问题:

1. const对象可以调用非const成员函数吗?不可以,权限不能放大

2. 非const对象可以调用const成员函数吗?可以,权限可以缩小

3. const成员函数内可以调用其它的非const成员函数吗?

4. 非const成员函数内可以调用其它的const成员函数吗?

8.取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date* operator&(){cout << "Date * operator&()" << endl;return this;}const Date* operator&()const{cout << "const Date * operator&()const" << endl;return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};
int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}

运行结果:

为什么这里要写两个取地址操作符重载呢?因为普通对象返回Date*,而const对象返回cosnt Date*,这两个是不同的,参数也是不同的,所以能构成重载。如果我们注释第一个非const成员函数。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date* operator&()//{//	cout << "Date * operator&()" << endl;//	return this;//}const Date* operator&()const{cout << "const Date * operator&()const" << endl;return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};
int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}

运行结果:

此时我们发现就会调用非cosnt成员函数,此时发生了参数和返回值权限的缩小。这个就和我们吃饭一样,有好吃的有自己相吃的就去吃,没有的就将就一下。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date* operator&()//{//	cout << "Date * operator&()" << endl;//	return this;//}/*const Date* operator&()const{cout << "const Date * operator&()const" << endl;return this;}*/
private:int _year; // 年int _month; // 月int _day; // 日
};
int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}

运行结果:

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容!

​​​​​​​

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

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

相关文章

迅为龙芯3A5000主板,支持PCIE 3.0、USB 3.0和 SATA 3.0显示接口2 路、HDMI 和1路 VGA,可直连显示器

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 桥片采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0显示接口2 路、HDMI 和1路 …

Leetcode---370周赛

题目列表 2923. 找到冠军 I 2924. 找到冠军 II 2925. 在树上执行操作以后得到的最大分数 2926. 平衡子序列的最大和 一、找到冠军I 第一题模拟题&#xff0c;简单来说是看每一行(列)是否全是1&#xff0c;当然不包括自己比自己强的情况&#xff0c;需要特判 代码如下 …

xdcms漏洞合集-漏洞复现

目录 xdcms v3.0.1漏洞 环境搭建 代码审计 目录总览 配置文件总览 登陆处sql注入 漏洞分析 漏洞复现 注册处sql注入漏洞 漏洞分析 漏洞复现 getshell 任意文件删除 xdcms订餐网站管理系统v1.0漏洞 简介 环境搭建 全局变量的覆盖 漏洞分析 漏洞复现 后台任意…

Android平台上执行C/C++可执行程序,linux系统编程开发,NDK开发前奏。

Android平台上执行C/C可执行程序&#xff0c;linux系统编程开发&#xff0c;NDK开发前奏准备。 1.下载NDK&#xff0c;搭建NDK开发环境 下载地址 https://developer.android.com/ndk/downloads 下载过程中点击下面箭头的地方&#xff0c;点击鼠标右键&#xff0c;复制好下载…

分享vmware和Oracle VM VirtualBox虚拟机的区别,简述哪一个更适合我?

VMware和Oracle VM VirtualBox虚拟机的区别主要体现在以下几个方面&#xff1a; 首先两种软件的安装使用教程如下&#xff1a; 1&#xff1a;VMware ESXI 安装使用教程 2&#xff1a;Oracle VM VirtualBox安装使用教程 商业模式&#xff1a;VMware是一家商业公司&#xff0c;而…

ubuntu 安装redis详细教程

下载redis安装包 链接如下&#xff1a; http://redis.io/download 本例版本为&#xff1a;redis-7.2.3.tar.gz 下载安装包到目录/opt下&#xff0c;路径可修改&#xff0c;本例为/opt wget https://github.com/redis/redis/archive/7.2.3.tar.gz 解压安装包&#xff0c;并…

整理笔记——稳压直流电路知识

一、稳压直流电路的基本构成 稳压直流电路就是把生活中常用的220V交流电转换成稳压直流电。如生活中常见的手机充电器就是一个稳压直流电路。其主要功能是提供持续稳定且满足要求的电压。 直流稳压电路由一下几个模块组成&#xff1a; 下面具体分析下各个模块的功能。…

个人前端编程技巧总结

目录 1. 让界面位于当前屏幕的中心&#xff08;屏幕中心&#xff09;css代码示例 2. 界面透明但是内部元素不透明&#xff08;毛玻璃&#xff09;css代码示例 3. 将当前界面的参数传递到跳转的目标页面&#xff08;携参跳转&#xff09;js代码 1. 让界面位于当前屏幕的中心&…

API接口自动化测试

本节介绍&#xff0c;使用python实现接口自动化实现。 思路&#xff1a;讲接口数据存放在excel文档中&#xff0c;读取excel数据&#xff0c;将每一行数据存放在一个个列表当中。然后获取URL,header,请求体等数据&#xff0c;进行请求发送。 结构如下 excel文档内容如下&#x…

Python的版本如何查询?

要查询Python的版本&#xff0c;可以使用以下方法之一&#xff1a; 1.在命令行中使用python --version命令。这会显示安装在计算机上的Python解释器的版本号。 # Author : 小红牛 # 微信公众号&#xff1a;wdPython2.在Python脚本中使用import sys语句&#xff0c;然后打印sy…

Django(二、静态文件的配置、链接数据库MySQL)

文章目录 一、静态文件及相关配置1.以登录功能为例2.静态文件3.资源访问4.静态文件资源访问如何解决&#xff1f; 二、静态文件相关配置1. 如何配置静态文件配置&#xff1f;2.接口前缀3. 接口前缀动态匹配4. form表单请求方法补充form表单要注意的点 三、request对象方法reque…

【服务发现与配置】Consul特性及搭建

文章目录 一、前言二、概念2.1、什么是Consul&#xff1f;2.2、Consul具有哪些特点?2.3、Consul 架构图2.4、Consul的使用场景 三、安装3.1. 下载3.2. 解压3.3. 拷贝到usr目录下3.4. 查看 安装是否成功3.5. 启动 四、Consul 开机自启动4.1. 路径/usr/lib/systemd/system/&…

前端工程化(vue2)

一、环境准备 1.依赖环境&#xff1a;NodeJS 官网&#xff1a;Node.js 2.脚手架&#xff1a;Vue-cli 参考网址&#xff1a;安装 | Vue CLI 介绍&#xff1a;Vue-cli用于快速的生成一个Vue的项目模板。主要功能有&#xff1a;统一的目录结构&#xff0c;本地调试&#xff0…

电脑msvcp110.dll丢失怎么办,msvcp110.dll缺失的详细修复步骤

在现代科技发展的时代&#xff0c;电脑已经成为我们生活和工作中不可或缺的工具。然而&#xff0c;由于各种原因&#xff0c;电脑可能会出现一些问题&#xff0c;其中之一就是msvcp110.dll文件丢失。这个问题可能会导致一些应用程序无法正常运行&#xff0c;给我们的生活和工作…

muduo源码剖析之TcpClient客户端类

简介 muduo用TcpClient发起连接&#xff0c;TcpClient有一个Connector连接器&#xff0c;TCPClient使用Conneccor发起连接, 连接建立成功后, 用socket创建TcpConnection来管理连接, 每个TcpClient class只管理一个TcpConnecction&#xff0c;连接建立成功后设置相应的回调函数…

虚拟机复制后,无法ping通问题解决

虚拟机复制后&#xff0c;无法ping通问题解决 可能出现的现象 ssh工具连接不上虚拟机&#xff1b;虚拟机ping不通外网或者ping不通内网其它虚拟机&#xff1b; 原因 原虚拟机和新复制出来的虚拟机的ip地址重复&#xff1b;原虚拟机和新复制出来的虚拟机的MAC地址重复&#…

基于SSM的建筑装修图纸管理平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【Hugging Face】如何下载模型文件

参考文章&#xff1a; 1、mac安装Homebrew - 知乎 2、 ssh连接 git lfs install git clone githf.co:bert-base-uncased -- 安装Homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" -- 配置文件生效 source /Use…

本地数据库迁移到云端服务器

工具迁移xtrabackup 创建云服务器——通过云服务器提供的公网地址远程连接XShell——利用迁移工具将数据库从本地迁移到云服务器 &#xff08;1&#xff09;创建云服务器 &#xff08;2&#xff09;远程连接XShell &#xff08;3&#xff09;yum安装mysql &#xff08;4&…

多语言翻译软件 Mate Translate mac中文版特色功能

Mate Translate for Mac是一款多语言翻译软件&#xff0c;Mate Translate mac可以帮你翻译超过100种语言的单词和短语&#xff0c;使用文本到语音转换&#xff0c;并浏览历史上已经完成的翻译。你还可以使用Control S在弹出窗口中快速交换语言。 Mate Translate Mac版特色功能…