当前位置: 首页 > news >正文

【C++】类和对象2.0

俺来写笔记了,哈哈哈,浅浅介绍类和对象的知识点!

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

俺们定义一个空类:

class N
{};

似乎这个类N里面什么都没有,其实不是这样子的。这个空类有6个默认的成员函数 。

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

其实不单止是空类,任何类在什么默认成员函数都不写时,编译器会自动生成以上6个默认成员函数。任何类如果没有显式实现上面的6个默认成员函数的某些个默认成员函数,编译器就会自动生成那些没有显式实现的默认成员函数。

如果懵懵的,没关系,下面鼠鼠会详细介绍!

2.构造函数

注意:构造函数是默认成员函数之一。

2.1.构造函数的概念

我们创建对象很多时候都希望对象使用前能被初始化,如果还是用之前学的Init方法初始化,如下:

class people
{const char* _name;int _age;
public:void Init(const char*name,int age){this->_name = name;this->_age = age;}
};
int main()
{people HD;HD.Init("HD", 20);people LCD;LCD.Init("LCD", 20);return 0;
}

每次创建对象都要调用Init,太麻烦,所以C++的类有了构造函数: 

构造函数简单来说就是初始化用的:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象(类的实例化或者对象实例化)时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。而且C++规定,对象实例化必须调用构造函数。

 构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。开空间创建对象那是类的实例化(或者说对象实例化)。

附:类的实例化具体知识请看【C++】类和对象1.0

2.2.构造函数的特性


特性如下,有7点:

  • 特性1:函数名与类名相同。
  • 特性2:构造函数可以重载,可以有参数也可以没有参数。
  • 特性3:无返回值,这里不是void,是就不需要写返回值。
  • 特性4:对象实例化(也就是类的实例化)时编译器自动调用对应的构造函数。

举个栗子来印证上面4点特性:

class people
{const char* _name;int _age;
public:people()//无参的构造函数{this->_name = "HD";this->_age = 20;}people(const char* name,int age)//带参的构造函数{this->_name = name;this->_age = age;}
};
int main()
{people HD;//调用无参的构造函数people LCD("LCD",20);//调用带参的构造函数return 0;
}

我们看people类里面本鼠显示实现了2个构造函数:一个无参、另一个带参, 函数名与类名(people)相同,无返回值,这2个构造函数构成函数重载。对象实例化时自动调用相应的构造函数。

附:函数重载知识请看【C++】C++入门1.0

同志们不相信对象实例化时自动调用相应的构造函数的话,大可去调试一下。本鼠就将调试模式下的监视窗口打开给同志们看看,可以看到确实初始化了:

我们再注意主函数内的写法:

int main()
{people HD;//调用无参的构造函数people LCD("LCD",20);//调用带参的构造函数return 0;
}

奇奇怪怪的吧?但是语法就是这样的。 第一条语句类实例化对象HD时会自动调用无参的构造函数,因为没有像第二条语句那样给参数。第二条语句类实例化对象LCD时会自动调用带参的构造函数,因为对象名后面跟了("LCD",20),这个东西就是传递给带参的构造函数做形参的。

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。

错误写法:

class people
{const char* _name;int _age;
public:people()//无参的构造函数{this->_name = "HD";this->_age = 20;}
};
int main()
{people HD();//warning C4930: “people HD(void)”: 未调用原型函数(是否是有意用变量定义的?)return 0;
}

其实上面的2个构造函数我们可以合成1个有全缺省参数的构造函数,功能也是一模一样的,如下:

附:缺省参数知识请看【C++】C++入门1.0

class people
{const char* _name;int _age;
public:people(const char* name="HD", int age=20)//全缺省参数的构造函数{this->_name = name;this->_age = age;}
};
int main()
{people HD;people LCD("LCD",20);return 0;
}

需要注意的是:全缺省参数的构造函数和无参的构造函数理论上是构成函数重载,是可以同时存在的。但实际上它们2个函数却不能同时存在,因为对象实例化时可能存在歧义。比如:

class people
{const char* _name;int _age;
public:people(const char* name = "HD", int age = 20)//全缺省参数的构造函数{this->_name = name;this->_age = age;}people(){this->_name = "HD";this->_age = 20;}
};
int main()
{people HD;//error C2668: “people::people”: 对重载函数的调用不明确people LCD("LCD", 20);return 0;
}

编译报错,因为实例化对象HD时,编译器不知道该调用哪一个构造函数!! 


  •  特性5:如果类中没有显式定义任何一个构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义了任何一个构造函数,编译器将不再生成。
class people
{const char* _name;int _age;
public:people(const char* name , int age ){this->_name = name;this->_age = age;}
};
int main()
{people HD;//error C2512: “people”: 没有合适的默认构造函数可用return 0;
}

看到这里报错了:已经显示定义了一个构造函数,那么编译器不再生成无参的默认构造函数。导致对象HD实例化时没有合适的默认构造函数可用。


  •  特性6:无参的构造函数和全缺省参数的构造函数都称为默认构造函数,所以默认构造函数有3个:无参的构造函数、全缺省参数的构造函数、我们没写编译器默认生成的构造函数(无参的,特性5介绍过)。并且默认构造函数只能有一个。 

为什么默认构造函数只能有一个?

前面我们介绍了全缺省参数的构造函数和无参的构造函数不能共存,并且一旦用户显式定义了任何一个构造函数,编译器不再生成默认的构造函数。那么这3个默认构造函数两两互斥,所以只能有一个。


那么我们来探讨一下编译器生成的默认构造函数对于对象内不同类型的成员会有什么行为?

  • 特性7:编译器生成的默认构造函数对于对象内内置类型成员不做处理(不排除有些编译器会去处理对象内内置类型成员,处理方式未知),对于对象内自定义类型成员会去调用它的默认构造函数(俺可没说对于对象自定义类型成员会去调用它的编译器生成的默认构造函数哈,俺说的是会去调用它的默认构造函数)。

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

class Time
{int _hour;int _minute;int _second;
public:Time(){this->_hour = 12;this->_minute = 20;this->_second = 5;}
};
class Date
{//内置类型int _year;int _month;int _day;//自定义类型Time _t;
};
int main()
{Date d;return 0;
}

同志们看好了。编译器生成的默认构造函数对于对象d的内置类型成员不做处理,所以可以看到_year、_month、_day被初始化成了随机值;对于自定义类型成员_t回去调用了它的默认构造函数Time(),所以可以看到_hour、_minute、_second被初始成了12、20、5。 

注意:C++11 中针对对象内置类型成员不做处理的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值(缺省值)。

class Time
{int _hour;int _minute;int _second;
public:Time(){this->_hour = 12;this->_minute = 20;this->_second = 5;}
};
class Date
{//内置类型int _year = 2024;int _month = 6;int _day = 17;//自定义类型Time _t;
};
int main()
{Date d;return 0;
}

 同志们看:因为内置类型成员在类中声明时给了默认值,所以编译器生成的默认构造函数将对象内置类型成员初始化成了默认值。。。


介绍完构造函数,我们要明白需要具体分析对象的初始需求,需要我们自己显式定义构造函数我们就自己定义,不需要就让编译器自动生成。但是大多数情况下都是要我们显式定义的。

2.3.构造函数的调用顺序

对象先定义的先构造,后定义的后构造。

比如:

#include<iostream>
using namespace std;
class Date
{int _year;
public:Date(int n)//构造函数{cout << "Date:" << n << endl;}
};
void Test()
{static Date d9(9);Date d10(10);
}
const Date d1(1);
static Date d2(2);
Date d3(3);
static Date d4(4);
int main()
{Date d5(5);const Date d6(6);static Date d7(7);Date d8(8);Test();const Date d11(11);return 0;
}

看结果,没问题的,意料之中:

3.析构函数

注意:析构函数是默认成员函数之一。

3.1.析构函数的概念

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

对象的销毁是当它的生命周期结束时编译器销毁的。

3.2.析构函数的特性

特性如下,有6点:


  • 特性1:析构函数名是在类名前加上字符 ~。 
  •  特性2:无参数无返回值类型。这里的返回值不是void,就是不需要写。 
  •  特性3:一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
  •  特性4:对象生命周期结束时,C++编译系统系统自动调用析构函数。

俺拿之前介绍过的栈举例印证以上4点特性:

#include<stdlib.h>
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this->_a = nullptr;this->_capacity = this->_top = 0;}void Push(const Stdatatype& n)//入栈{if (this->_top == this->_capacity){int newcapacity = (this->_capacity == 0) ? 4 : (this->_capacity * 2);Stdatatype* tmp = (Stdatatype*)realloc(this->_a, newcapacity * sizeof(Stdatatype));if (tmp == nullptr){perror("realloc fail");exit(-1);}this->_capacity = newcapacity;this->_a = tmp;}this->_a[this->_top++] = n;}//Pop、Top、Size……函数略~Stack()//析构函数{free(this->_a);this->_a = nullptr;this->_capacity = this->_top = 0;}
};
int main()
{Stack s;s.Push(1);s.Push(2);return 0;
}

 同志们请看,当s.Push(2);执行完成后对象s的成员如图:

当执行完return 0;后,对象s的成员资源就被清理掉了,比如realloc在堆区申请的空间就被free掉了!! 

其实说白了,这个析构函数的功能大抵上就跟前面博客介绍过的栈的销毁栈一样。析构函数本身就是清理资源的,不同的是析构函数当对象生命周期结束时,C++编译系统系统自动调用析构函数,但是销毁栈的StackDestroy函数却需要手动调用。。。


  • 特性5:编译器自动生成的析构函数(当我们没有显式定义时会生成)跟编译器生成的默认构造函数类似,对于对象内内置类型成员不做处理,对于对象内自定义类型成员会去调用它的析构函数。

看这个代码:

#include<iostream>
using namespace std;
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()。就说明了编译器自动生成的析构函数对象内自定义类型成员会去调用它的析构函数。注意:析构函数当对象生命周期结束时,C++编译系统系统自动调用析构函数。

同志们请看,编译器自动生成的析构函数对对象内内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可。


  • 特性6:如果类中没有申请资源时,析构函数可以不写(显式定义),直接使用编译器生成的默认析构函数。有资源申请时,一定要写,否则会造成资源泄漏。

本鼠举一个不用显式定义析构函数的栗子:

class Date
{int _year;int _month;int _day;
public:Date(int year=2024, int month=6, int day=17){this->_year = year;this->_month = month;this->_day = day;}
};
int main()
{Date d1;Date d2(203, 12, 12);return 0;
}

这个Date类中并没有申请资源,我们用编译器生成的默认析构函数就行,不用自己写。

再举一个需要显式定义析构函数的栗子:

#include<stdlib.h>
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this->_a = nullptr;this->_capacity = this->_top = 0;}void Push(const Stdatatype& n)//入栈{if (this->_top == this->_capacity){int newcapacity = (this->_capacity == 0) ? 4 : (this->_capacity * 2);Stdatatype* tmp = (Stdatatype*)realloc(this->_a, newcapacity * sizeof(Stdatatype));if (tmp == nullptr){perror("realloc fail");exit(-1);}this->_capacity = newcapacity;this->_a = tmp;}this->_a[this->_top++] = n;}//Pop、Top、Size……函数略~Stack()//析构函数{free(this->_a);this->_a = nullptr;this->_capacity = this->_top = 0;}
};
int main()
{Stack s;s.Push(1);s.Push(2);return 0;
}

这里为什么要自己写析构函数?

我们来看上面代码映射到内存上的想象图就很明白了:

如果我们不手动写析构函数,那么编译器就会自动生成默认的析构函数。当对象s生命周期结束时会调用编译器自动生成的析构函数。该函数对对象内内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收,那么对象s在栈区的空间的使用权就还给操作系统了,但是堆区的资源还没有释放,这样子不就造成内存泄漏了吗!!!


3.3.析构函数的调用顺序

析构函数的调用顺序满足:局部非静态对象(先定义的后析构(相对的))——>局部静态对象(先定义的后析构(绝对的))——>全局对象(先定义的后析构(绝对的))。举例证明:

#include<iostream>
using namespace std;
class Date
{int _year;
public:~Date()//析构函数{cout << this->_year << endl;}Date(int n)//构造函数{_year = n;}
};
void Test()
{static Date d7(7);Date d1(1);
}
const Date d12(12);
static Date d11(11);
Date d10(10);
static Date d9(9);
int main()
{Date d5(5);const Date d4(4);static Date d8(8);Date d3(3);Test();const Date d2(2);static Date d6(6);return 0;
}

意料之中,多观察可以发现规律的:

 

4.拷贝构造函数 

注意:这是构造函数之一,也是默认成员函数之一。

4.1.拷贝构造函数的概念

拷贝构造函数的大概作用是:在创建新对象时,使得新对象与已经存在的对象一模一样。

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

4.2.拷贝构造函数的特性

特性如下,有4点:


  • 特性1:拷贝构造函数是构造函数的一个重载形式。也就是说拷贝构造函数也是一种构造函数,形象的说拷贝构造函数是构造函数的一个子集。
  • 特性2:拷贝构造函数的参数只有一个且必须是类类型对象的引用(条件允许的情况下最好用常引用),使用传值方式编译器直接报错, 因为会引发无穷递归调用。
class Date
{int _year;int _month;int _day;
public:Date(int year=2024, int month=6, int day=17)//(全缺省)构造函数{this->_year = year;this->_month = month;this->_day = day;}Date(const Date& d)//拷贝构造函数{this->_year = d._year;this->_month = d._month;this->_day = d._day;}
};
int main()
{Date d1;//调用(全缺省)构造函数Date d2(d1);//调用拷贝构造函数,写法1Date d3 = d2;//调用拷贝构造函数,写法2return 0;
}

因为拷贝构造函数是构造函数的一个子集,所以拷贝构造函数拥有构造函数的共性: 函数名与类名相同、无返回值、创建新对象时由编译器自动调用。

想通过拷贝构造函数创建对象有两种写法,如上代码主函数内可见。要注意这两种写法所面临的同类型对象都是一个已经存在的对象一个正在创建的对象

调试结果如图: 

至于特性2所说的拷贝构造函数的参数使用传值方式编译器直接报错, 因为会引发无穷递归调用。为什么会引发无穷递归调用呢?

 因为C++规定自定义类型的拷贝都会通过调用拷贝构造函数来拷贝。如果将拷贝构造函数的参数写成传值方式,传值的方式中形参是实参的一份临时拷贝。那么当同志们想要调用拷贝构造函数,就要先传参,传参又要拷贝实参给形参,既然是自定义类型拷贝又要调用拷贝构造函数,调用拷贝构造函数又要传参,传参又要拷贝实参给形参……这不就无穷递归调用了吗?

而如果拷贝构造函数的参数使用传引用传参,就不存在形参是实参的拷贝问题,就不会无穷递归调用。


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

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

举个栗子编译器生成默认的拷贝构造函数完成浅拷贝的栗子:

#include<iostream>
using namespace std;
class Time
{int _hour;int _minute;int _second;
public:Time(){this->_hour = 14;this->_minute = 57;this->_second = 30;}Time(const Time& d){this->_hour = d._hour;this->_minute = d._minute;this->_second = d._second;cout << "真的调用了这个函数捏!" << endl;}
};
class Date
{//内置类型int _year = 2024;int _month = 6;int _day = 18;//自定义类型Time _t;
};
int main()
{Date d1;//调用编译器生成的默认构造函数Date d2 = d1;//调用编译器生成的默认拷贝构造函数return 0;
}

本鼠先浅浅介绍一下这个代码:

看主函数第一条语句创建Date类类型的对象d1,由于Date类中没有显式定义构造函数,就通过编译器生成的默认构造函数创建对象d1,由于内置类型成员变量在类的声明中给了缺省值,这个编译器生成的默认构造函数就将d1的内置类型成员变量初始化成了缺省值,对于d1的自定义类型成员变量_t,去调用它的默认构造函数Time()(本鼠显式定义了),所以d1的自定义类型成员变量_t的成员变量分别被初始化成了14、57、30。

看主函数第二条语句通过拷贝构造函数创建与已存在对象d1一模一样的同类型对象d2,由于Date没有显式定义拷贝构造函数,就通过编译器生成的默认拷贝构造函数创建对象d2,这个编译器生成的默认拷贝构造函数对对象d1的内置类型成员浅拷贝给新对象d2的内置类型成员,对于对象d1的自定义类型成员_t去调用它的拷贝构造函数Time(const Time&)(本鼠显示定义了)进行浅拷贝,所以d2的自定义类型成员变量_t的成员变量分别为14、57、30。

第三条语句结束后编译器生成的默认析构函数先后作用于d2和d1。

 同志们请看,确实是完成了浅拷贝:

同志们请看,也确实调用了内置类型成员变量_t的拷贝构造函数:

问题又来了:编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 

答案是有些需要,有些不需要。类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会出问题。

看下面的代码:

#include<stdlib.h>
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this->_a = nullptr;this->_capacity = this->_top = 0;}void Push(const Stdatatype& n)//入栈{if (this->_top == this->_capacity){int newcapacity = (this->_capacity == 0) ? 4 : (this->_capacity * 2);Stdatatype* tmp = (Stdatatype*)realloc(this->_a, newcapacity * sizeof(Stdatatype));if (tmp == nullptr){perror("realloc fail");exit(-1);}this->_capacity = newcapacity;this->_a = tmp;}this->_a[this->_top++] = n;}//Pop、Top、Size……函数略~Stack()//析构函数{free(this->_a);this->_a = nullptr;this->_capacity = this->_top = 0;}
};
int main()
{Stack s1;s1.Push(1);Stack s2(s1);return 0;
}

 编译运行如图,为什么会这样子呢?

其实就是我们没有显式定义拷贝构造函数,导致通过编译器生成的默认拷贝构造函数创建对象s2,这个编译器生成的默认拷贝构造函数是对s1浅拷贝给s2的,我们可以调试看看浅拷贝:

 浅拷贝在这里导致对象s1和对象s2的成员变量_a都指向同一块空间,想象一下:

这里浅拷贝显然不是我们想要的。而且当析构函数作用于对象s2时,堆区动态开辟的那块空间已经被free掉了,此时s1._a就是野指针,再当析构函数于对象s1时,那块空间再次被free,同一块空间被free两次导致报错!

所以像这种类中如果涉及资源申请的,我们需要自己写拷贝构造函数以实现深拷贝,而不是使用编译器自动生成的默认拷贝构造函数,正确写法如下:

#include<stdlib.h>
#include<string.h>
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this->_a = nullptr;this->_capacity = this->_top = 0;}void Push(const Stdatatype& n)//入栈{if (this->_top == this->_capacity){int newcapacity = (this->_capacity == 0) ? 4 : (this->_capacity * 2);Stdatatype* tmp = (Stdatatype*)realloc(this->_a, newcapacity * sizeof(Stdatatype));if (tmp == nullptr){perror("realloc fail");exit(-1);}this->_capacity = newcapacity;this->_a = tmp;}this->_a[this->_top++] = n;}//Pop、Top、Size……函数略Stack(const Stack& s)//拷贝构造函数{Stdatatype* tmp = (Stdatatype*)malloc(sizeof(Stdatatype) * s._capacity);if (tmp == NULL){perror("malloc fail");exit(-1);}memcpy(tmp, s._a, sizeof(Stdatatype) * s._top);this->_a = tmp;this->_capacity = s._capacity;this->_top = s._top;}~Stack()//析构函数{free(this->_a);this->_a = nullptr;this->_capacity = this->_top = 0;}
};
int main()
{Stack s1;s1.Push(1);Stack s2(s1);return 0;
}

自己写的拷贝构造函数完成深拷贝,想象图如下:

显然这样的拷贝才是我们想要的。。


  • 特性4:拷贝构造函数典型调用场景:
  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象  
#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;
}

在x86环境下运行结果:

 运行结果打印了6条语句,因为:

第1条语句:主函数第1条语句Date d1(2022,1,13):调用构造函数创建对象d1;

第2条语句:主函数第2条语句Test(d2):d1作为实参以传值方式传递给Test函数的形参,调用了拷贝构造函数;

第3条语句:Test函数第1条语句Date temp(d):调用构造函数创建对象temp;

第4条语句:析构函数作用于Test函数中的对象temp;

第5条语句:析构函数作用于Test函数返回时创建的临时对象;

第6条语句:析构函数作用于主函数中的对象d1。

我们看到Test函数以传值返回,按道理说应该调用拷贝构造函数拷贝以temp为蓝本的临时对象用于返回,但是这里没有调用拷贝构造函数,因为这个过程被编译器优化了,检测到构建的局部变量temp用于返回,直接就在上一个函数栈构造这个对象,用于返回,这样就只调用了一次构造,而不需要进行拷贝构造。


5.赋值运算符重载

 注意:赋值运算符重载是默认成员函数之一。

赋值运算符重载是运算符重载的一种,所以本鼠先要介绍运算符重载的知识!

5.1.运算符重载

5.1.1.运算符重载

我们来看一个代码:

#include<iostream>
using namespace std;
int main()
{int a = 10;int b = 20;cout << (a < b) << endl;//<<优先级高于<,所以使用()控制return 0;
}

内置类型的对象想要使用运算符实现运算符的功能可以直接使用,如本代码中想要知道a是否小于b就直接用运算符<呗。运行结果如图:

但是自定义类型对象却无法直接使用这些现成的运算符,比如:

#include<iostream>
using namespace std;
class Date
{int _year;int _month;int _day;
};
int main()
{Date a;Date b;cout << (a < b) << endl;return 0;
}

编译绝对会报错!因为操作数a和b对于操作符<来说该有怎么样的行为是不明确的。换句话说,操作符<不知道该如何比较a和b。

但是我们不乏需要比较自定义类型对象的行为,我们当然可以定义一个函数来比较,比如:

#include<iostream>
#include<stdbool.h>
using namespace std;
class Date
{
public:int _year;int _month;int _day;Date(int year,int month,int day){this->_year = year;this->_month = month;this->_day = day;}
};
bool Lessthan(const Date& a, const Date& b)
{if (a._year < b._year){return true;}else if (a._year == b._year){if (a._month < a._month){return true;}else if (a._month == b._month){return a._day < b._day;}}return false;
}
int main()
{Date a(2003, 12, 12);Date b(2003, 12, 5);cout << Lessthan(a, b) << endl;return 0;
}

我们看运行结果就可以知道a“不小于”b:

 

可是这样子的代码可读性就不是很好了。我们要分析Lessthan函数的行为才知道原来这个函数是检测形参a是否小于形参b的。如果我们对于自定义类型对象能直接使用操作符(例如<)就好了,一眼定真就知道在干啥。


同志们,所以C++有了运算符重载,请看:

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

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

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

一个使用运算符重载的栗子:

#include<iostream>
#include<stdbool.h>
using namespace std;
class Date
{
public:int _year;int _month;int _day;Date(int year,int month,int day){this->_year = year;this->_month = month;this->_day = day;}
};
bool operator<(const Date& a, const Date& b)
{if (a._year < b._year){return true;}else if (a._year == b._year){if (a._month < a._month){return true;}else if (a._month == b._month){return a._day < b._day;}}return false;
}
int main()
{Date a(2003, 12, 12);Date b(2003, 12, 5);cout << operator<(a, b) << endl;//正常的函数显示调用cout << (a < b) << endl;//在编译器看来与上一条语句一模一样,效果相同。return 0;
}

运行结果: 

通过运算符重载,对于自定义类型对象可以直接使用操作符(比如<),代码可读性大大提升!

对于这个使用运算符重载的栗子,我们还可以分析:

这里会发现运算符重载成全局的函数就需要成员变量是公有的,不然受到访问限定符的作用无法访问类对象的成员变量,那么问题来了,封装性如何保证?

这里其实可以用以下办法解决:

  1. 办法1:友元声明解决(本鼠后面博客会介绍的)。
  2. 办法2:或者干脆重载成类成员函数。
  3.  办法3:或者定义一些类成员函数用于“调出”成员变量。

使用办法3,本鼠举例:

#include<iostream>
#include<stdbool.h>
using namespace std;
class Date
{int _year;int _month;int _day;
public:int& Getyear(){return _year;}int& Getmonth(){return _month;}int& Getday(){return _day;}Date(int year,int month,int day){this->_year = year;this->_month = month;this->_day = day;}
};
bool operator<( Date& a, Date& b)
{if (a.Getyear()<b.Getyear()){return true;}else if (a.Getyear() == b.Getyear()){if (a.Getmonth() < a.Getmonth()){return true;}else if (a.Getmonth() == b.Getmonth()){return a.Getday() < b.Getday();}}return false;
}
int main()
{Date a(2003, 12, 12);Date b(2003, 12, 5);cout << operator<(a, b) << endl;cout << (a < b) << endl;//在编译器看来与上一条语句一模一样,效果相同。return 0;
}

运行结果:

 


运算符重载有几个注意事项:

  • 不能通过连接其他符号来创建新的操作符:比如operator@,就是说重载的操作符必须是C/C++语法存在的。
  • 重载操作符必须有一个类类型参数,就是说不能通过运算符重载来改变运算符对内置类型的行为。
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义(虽然语法上可以编译通过,但是这样子的话可读性还不如之前了)。
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针。
  • .* 和::sizeof?: . 这5个运算符不能重载。

举个栗子:

#include<iostream>
using namespace std;
class exmple
{int _data1;int _data2;
public:exmple (int data1,int data2){_data1 = data1;_data2 = data2;}exmple operator+(const exmple& n){exmple tmp(0, 0);tmp._data1 = this->_data1 - n._data1;tmp._data2 = this->_data2 - n._data2;return tmp;}void Print(){cout << _data1 << ' ' << _data2;}
};
int main()
{exmple a(10, 10);exmple b(5, 5);exmple c = a + b;//this指针传递&a,形参n是b的引用c.Print();return 0;
}

我们看类成员函数operator+的形参为啥只有一个,那是因为隐藏的this指针帮忙传递了一个。 看结果:

结果是两个5。上面的代码就是一个错误示范,我们想要重载运算符+,但是内部逻辑却写成了-,导致可读性大大降低,犯了注意事项3的错误。


5.1.2.++和--重载 

为什么要强调这两个运算符的重载呢?

拿++来说,前置++和后置++都是一样的运算符,那么运算符重载的时候如何形成正确重载?函数名都写成operator++的话无法区分啊!

所以,C++规定:后置++重载时多增加一个int类型的参数,但当我们不显示调用函数时该参数不用传递,编译器自动传递。这个参数的参数名我们甚至可以不显示写。

#include<iostream>
using namespace std;
class exmple
{int _data1;int _data2;
public:exmple& operator++()//前置++{_data1++;_data2++;return *this;}exmple operator++(int)//后置++,没显示写参数名,也可以写成exmple operator++(int n){exmple tmp = *this;//拷贝构造++*this;return tmp;}exmple(int data1, int data2)//构造函数{_data1 = data1;_data2 = data2;}void Print(){cout << _data1 << ' ' << _data2 << endl;}
};
int main()
{exmple a(10, 10);exmple b(10, 10);exmple c = a++;exmple d = ++b;c.Print();d.Print();return 0;
}

 

上面有一些注意事项:

  • 如果我们打算显示调用后置++这个函数,那么一定要传入一个任意int整形以区分前置++,不然编译器就会去调用前置++了。

显示调用后置++时正确写法:

exmple c = a.operator++(10);//相当于exmple c = a++;

  • 后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要用临时对象先将*this保存 一份,然后给*this+1,返回临时对象。因为返回是临时对象,因此只能以值的方式返回,不能返回引用。

前置--和后置--同理,不解释了!!

5.2.赋值运算符重载

 介绍了运算符重载,那么赋值运算符重载的意思就很简单了。赋值运算符重载格式:

  1. 参数类型:const T&,传递引用可以提高传参效率(T是自定义类型,下同)。
  2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值。
  3. 检测是否自己给自己赋值(可有可无,最好有)。
  4. 返回*this :要复合连续赋值的含义。

我们看赋值运算符应该支持连续赋值:

    int x, y, z;x = y = z = 10;//连续赋值

这里连续赋值的结合性是从右向左的 ,表达式z=10返回值是z本身(左操作数作为返回值),返回值z再作为表达式y=z的右操作数,表达式y=z返回值是y本身……

#include<stdio.h>
int main()
{int z = 10;int x = 20;printf("%p\n", &z);printf("%p\n", &(z = x));return 0;
}

 真的是返回左操作数本身啊!!

所以赋值运算符重载后也要支持连续赋值,要返回左操作数本身(其实返回其拷贝也行)。

#include<iostream>
using namespace std;
class exmple
{int _data1;int _data2;
public:exmple& operator=(const exmple& n)//赋值运算符重载{if (this != &n)//if用于检测是否自己给自己赋值{this->_data1 = n._data1;this->_data2 = n._data2;return *this;}}exmple(int data1, int data2)//构造函数{_data1 = data1;_data2 = data2;}void Print(){cout << _data1 << ' ' << _data2 << endl;}
};
int main()
{exmple a(10, 10);exmple b(20, 20);exmple c(30, 30);a = b = c;//连续赋值a.Print();b.Print();c.Print();return 0;
}

 赋值运算符重载所面临的同类型对象都是已经存在的对象。而拷贝构造函数所面临的对象一个是已经存在的对象,另一个是正在创建的对象

 还有:

  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数。原因:赋值运算符如果在类中不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
  2. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝(浅拷贝)。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。跟拷贝构造函数类似,不解释了。
  3. 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现,否则浅拷贝会出现一大堆问题,跟拷贝构造函数类似,不解释了。

6.const成员函数

看一个代码:

#include<iostream>
using namespace std;
class exmple
{int _data1=10;int _data2=20;
public:void Print(){cout << _data1 << ' ' << _data2 << endl;}
};
int main()
{const exmple a;a.Print();//error C2662: “void exmple::Print(void)”: 不能将“this”指针从“const exmple”转换为“exmple &”return 0;
}

编译不通过,为什么?

很简单,对象a是被const修饰的常对象 ,调用Print函数隐藏的this指针接收a的地址。如果编译通过,就可以通过this指针(类型是exmple*)间接修改常对象a,权限被放大了。

怎么解决呢?很简单,用const修饰this指针,被修饰后的this指针类型是const exmple*,这样指针指向的值就不可改了。 

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

可是this指针是隐藏的,我们如何将其用const修饰呢?很简单,举个栗子:

#include<iostream>
using namespace std;
class exmple
{int _data1 = 10;int _data2 = 20;
public:void Print()const//const成员函数,this指针类型是const exmple *{cout << _data1 << ' ' << _data2 << endl;}
};
int main()
{const exmple a;a.Print();return 0;
}

 没问题吧!

编译器对于const成员函数的处理用下代码举例:

需要注意的是:

  1. 如果类中const成员函数的声明和定义分离了,我们在声明和定义处都要”加上“const。
  2. 不是所有成员函数的第一个形参this指针都能被const修饰的。如果是一个对成员变量要进行读写访问的成员函数,不能”加“const,因为如果this指针被const修饰了就不能对类的任何成员进行修改了。

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

注意:取地址操作符重载和const取地址操作符重载都是默认成员函数之一。

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,因为无非就是返回对象地址罢了。只有特殊情况,才需要重载,比如想让别人获取到指定的内容!


取地址操作符重载举例:

#include<iostream>
using namespace std;
class exmple
{int _data1 = 10;int _data2 = 20;
public:exmple* operator&()//取地址操作符重载{return this;}
};
int main()
{exmple a;cout << &a << endl;//&a相当于a.operator()return 0;
}


const取地址操作符重载: 

我们要知道运算符重载本质是一个函数。所有取地址操作符重载也是一个函数。const取地址操作符重载的意思是取地址操作符重载的第一个形参this指针被const修饰了,const取地址操作符重载是一个const成员函数。

const取地址操作符重载举例:

#include<iostream>
using namespace std;
class exmple
{int _data1 = 10;int _data2 = 20;
public:const exmple* operator&()const//const取地址操作符重载{return this;}
};
int main()
{exmple a;cout << &a << endl;//&a相当于a.operator()return 0;
}

 

为啥const取地址操作符重载的返回值也要被const修饰呢?因为this指针是被const修饰的,将this指针返回,那么返回值当然要被const修饰了,不然就是权限的放大,编译会报错的。。


感谢阅读!!!如有错误,恳请斧正!! 

相关文章:

【C++】类和对象2.0

俺来写笔记了&#xff0c;哈哈哈&#xff0c;浅浅介绍类和对象的知识点&#xff01; 1.类的6个默认成员函数 俺们定义一个空类&#xff1a; class N {}; 似乎这个类N里面什么都没有&#xff0c;其实不是这样子的。这个空类有6个默认的成员函数 。 默认成员函数&#xff1a…...

【LLM之KG】KoPA论文阅读笔记

研究背景 知识图谱补全&#xff08;KGC&#xff09;是通过预测知识图谱中缺失的三元组来完善知识图谱的信息。传统方法主要基于嵌入和预训练语言模型&#xff0c;但这些方法往往忽视了知识图谱的结构信息&#xff0c;导致预测效果不佳。 研究目标 本文的研究目标是探索如何将…...

UI设计速成课:理解模态窗口与非模态窗口的区别

我们日常所说的弹性框架是非常笼统的概念。我们习惯性地称之为对话框架、浮动层和提示条。弹性框架可以分为两种:模态弹性框架和非模态弹性框架。产品需要弹性框架来传递信息&#xff0c;用户需要弹性框架来接受反馈&#xff0c;但是没有经过推敲的弹出窗口设计很容易让用户感到…...

【Linux】基础IO_4

文章目录 六、基础I/O4. 动静态库 未完待续 六、基础I/O 4. 动静态库 既然我们能够成功创建静态库了&#xff0c;接下来我们将这个代码打包成动态库&#xff1a; shared: 表示生成共享库格式 fPIC&#xff1a;产生位置无关码(position independent code) 动态库库名规则&…...

C++模板类原理讲解

C模板类原理讲解 C模板是一种强大的编译期工具&#xff0c;它允许我们创建通用的、类型无关的类和函数。模板的主要目的是实现代码的重用和泛型编程。模板类的原理涉及以下几个方面&#xff1a; 模板的定义和实例化模板的类型参数模板特化模板的编译过程模板的优点和缺点 1.…...

scratch编程03-反弹球

这篇文章和上一篇文章《scratch3编程02-使用克隆来编写小游戏》类似&#xff08;已经完全掌握了克隆的可以忽略这篇文章&#xff09;&#xff0c;两篇文章都使用到了克隆来编写一个小游戏&#xff0c;这篇文章与上篇文章不同的是&#xff0c;本体在进行克隆操作时&#xff0c;不…...

postgresql数据库进阶知识

postgresql数据库进阶知识 # 如果表存在就先删除 drop table if exists student; # 创建学生表 # id serial not null 表示id自增 # id integer not null 表示id不自增 create table student (id serial not nullconstraint student_pkprimary…...

关于HTTP劫持,该如何理解、防范和应对

一、引言 HTTP劫持&#xff08;HTTP Hijacking&#xff09;是一种网络安全威胁&#xff0c;它发生在HTTP通信过程中&#xff0c;攻击者试图通过拦截、篡改或监控用户与服务器之间的数据流量&#xff0c;以达到窃取敏感信息或执行恶意操作的目的。今天我们就来详细了解HTTP劫持…...

System.Data.OracleClient.OracleException:“ORA-12571: TNS: 包写入程序失败

System.Data.OracleClient.OracleException:“ORA-12571: TNS: 包写入程序失败 解决方法&#xff1a; 首先%oracle_home%/network/admin下的sqlnet.ora文件&#xff0c;把SQLNET.AUTHENTICATION_SERVICES (NTS)加个 # 注释掉就好了...

saas产品运营案例 | 联盟营销计划如何帮助企业提高销售额?

在当今数字化时代&#xff0c;SaaS&#xff08;软件即服务&#xff09;产品已成为企业提高效率、降低成本的重要工具。然而&#xff0c;面对激烈的市场竞争&#xff0c;如何有效地推广SaaS产品、提高销售额&#xff0c;成为许多企业面临的挑战。林叔将以ClickFunnels为例&#…...

模式分解算法-满足3NF的无损且保持函数依赖的分解算法、满足BCNF的无损连接分解算法

一、引言 1、对指定的关系模式&#xff0c;若范式级别较低&#xff0c;为第一范式或第二范式&#xff0c;由于存在数据冗余或更新异常问题&#xff0c;在实际中一般是不可用的&#xff0c;关系模式的规范化就是将满足低一级的关系模式分解为若干满足高一级范式的关系模式的集合…...

荷兰与法国战平,双方能携手出现?

就在昨天晚上&#xff0c;荷兰队经历了90分钟的鏖战&#xff0c;最终0-0与法国队握手言和。此役&#xff0c;哈维-西蒙斯为荷兰队打进一球&#xff0c;但进球被判无效。从目前的积分形势来看&#xff0c;双方基本上确定携手晋级16强赛。本场比赛&#xff0c;荷兰队后卫内森-阿克…...

数据可视化实验二:回归分析、判别分析与聚类分析

目录 一、使用回归分析方法分析某病毒是否与温度呈线性关系 1.1 代码实现 1.2 线性回归结果 1.3 相关系数验证 二、使用判别分析方法预测某病毒在一定的温度下是否可以存活&#xff0c;分别使用三种判别方法&#xff0c;包括Fish判别、贝叶斯判别、LDA 2.1 数据集展示&am…...

FL论文专栏|设备异构、异步联邦

论文&#xff1a;Asynchronous Federated Optimization&#xff08;12th Annual Workshop on Optimization for Machine Learning&#xff09; 链接 实现Server的异步更新。每次Server广播全局Model的时候附带一个时间戳&#xff0c;Client跑完之后上传将时间戳和Model同时带回…...

【Java毕业设计】基于JavaWeb的礼服租赁系统

文章目录 摘 要Abstract目录1 绪论1.1 课题背景和意义1.2 国内外研究现状1.2.1 国外研究现状 1.3 课题主要内容 2 开发相关技术介绍2.1 Spring Boot框架2.2 Vue框架2.3 MySQL数据库2.4 Redis数据库 3 系统分析3.1 需求分析3.1.1 用户需求分析3.1.2 功能需求分析 3.2 可行性分析…...

代码随想录训练营Day 66|卡码网101.孤岛的总面积、102.沉没孤岛、103.水流问题、104.建造最大岛屿

1.孤岛的总面积 101. 孤岛的总面积 | 代码随想录 代码&#xff1a;(bfs广搜) #include <iostream> #include <vector> #include <queue> using namespace std; int dir[4][2] {1,0,0,1,-1,0,0,-1}; int count; void bfs(vector<vector<int>>&a…...

根据状态转移写状态机-二段式

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 描述 题目描述&#xff1a; 如图所示为两种状态机中的一种&#xff0c;请根据状态转移图写出代码&#xff0c;状态转移线上的0/0等表示的意思是过程中data/flag的值。 要求&#xff1a; 1、 必须使用对应类型的状…...

PyTorch C++扩展用于AMD GPU

PyTorch C Extension on AMD GPU — ROCm Blogs 本文演示了如何使用PyTorch C扩展&#xff0c;并通过示例讨论了它相对于常规PyTorch模块的优势。实验在AMD GPU和ROCm 5.7.0软件上进行。有关支持的GPU和操作系统的更多信息&#xff0c;请参阅系统要求&#xff08;Linux&#xf…...

Hadoop archive

Index of /dist/hadoop/commonhttps://archive.apache.org/dist/hadoop/common/...

R语言——R语言基础

1、用repeat、for、while计算从1-10的所有整数的平方和 2、编写一个函数&#xff0c;给出两个正整数&#xff0c;计算他们的最小公倍数 3、编写一个函数&#xff0c;让用户输入姓名、年龄&#xff0c;得出他明年的年龄。用paste打印出来。例如&#xff1a;"Hi xiaoming …...

VFB电压反馈和CFB电流反馈运算放大器(运放)选择指南

VFB电压反馈和CFB电流反馈运算放大器(运放)选择指南 电流反馈和电压反馈具有不同的应用优势。在很多应用中&#xff0c;CFB和VFB的差异并不明显。当今的许多高速CFB和VFB放大器在性能上不相上下&#xff0c;但各有其优缺点。本指南将考察与这两种拓扑结构相关的重要考虑因素。…...

elasticsearch安装(centos7)

先给出网址 elasticsearch&#xff1a;Download Elasticsearch | Elastic elasticKibana&#xff1a;Download Kibana Free | Get Started Now | Elastic Logstash&#xff1a;Download Logstash Free | Get Started Now | Elastic ik分词&#xff1a;Releases infinilabs/…...

Java高手的30k之路|面试宝典|精通JVM(二)

JVM基本结构 类加载子系统&#xff1a;负责将.class文件加载到内存中&#xff0c;并进行验证、准备、解析和初始化。运行时数据区&#xff1a;包括堆&#xff08;Heap&#xff09;、方法区&#xff08;Method Area&#xff09;、Java栈&#xff08;Java Stack&#xff09;、本…...

JVM专题六:JVM的内存模型

前面我们通过Java是如何编译、JVM的类加载机制、JVM类加载器与双亲委派机制等内容了解到了如何从我们编写的一个.Java 文件最终加载到JVM里的&#xff0c;今天我们就来剖析一下这个Java的‘中介平台’JVM里面到底长成啥样。 JVM的内存区域划分 Java虚拟机&#xff08;JVM&…...

学习java第一百零七天

解释JDBC抽象和DAO模块 使用JDBC抽象和DAO模块&#xff0c;我们可以确保保持数据库代码的整洁和简单&#xff0c;并避免数据库资源关闭而导致的问题。它在多个数据库服务器给出的异常之上提供了一层统一的异常。它还利用Spring的AOP模块为Spring应用程序中的对象提供事务管理服…...

k8s上尝试滚动更新和回滚

滚动更新和回滚 实验目标&#xff1a; 学习如何进行应用的滚动更新和回滚操作。 实验步骤&#xff1a; 创建一个 Deployment。更新 Deployment 的镜像版本&#xff0c;观察滚动更新过程。回滚到之前的版本&#xff0c;验证回滚操作。 今天呢&#xff0c;我们继续来进行我们k…...

GitHub Copilot 登录账号激活,已经在IntellJ IDEA使用

GitHub Copilot 想必大家都是熟悉的&#xff0c;一款AI代码辅助神器&#xff0c;相信对编程界的诸位并不陌生。 今日特此分享一项便捷的工具&#xff0c;助您轻松激活GitHub Copilot&#xff0c;尽享智能编码之便利&#xff01; GitHub Copilot 是由 GitHub 和 OpenAI 共同开…...

进程知识点(二)

文章目录 一、进程关系&#xff1f;二、孤儿态进程(Orphan)定义危害处理 三、僵尸进程定义处理 四、守护进程(Daemon )定义作用 总结 一、进程关系&#xff1f; 亲缘关系&#xff1a;亲缘关系主要体现于父子进程&#xff0c;子进程父进程创建&#xff0c;代码继承于父进程&…...

【线性代数】【一】1.6 矩阵的可逆性与线性方程组的解

文章目录 前言一、求解逆矩阵二、线性方程组的解的存在性总结 前言 前文我们引入了逆矩阵的概念&#xff0c;紧接着我们就需要讨论一个矩阵逆的存在性以及如何求解这个逆矩阵。最后再回归上最初的线性方程组的解&#xff0c;分析其中的联系。 一、求解逆矩阵 我们先回想一下在…...

基于大型语言模型的全双工语音对话方案

摘要解读 我们提出了一种能够以全双工方式运行的生成性对话系统&#xff0c;实现了无缝互动。该系统基于一个精心调整的大型语言模型&#xff08;LLM&#xff09;&#xff0c;使其能够感知模块、运动功能模块以及一个具有两种状态&#xff08;称为神经有限状态机&#xff0c;n…...

Spring Boot集成Minio插件快速入门

1 Minio介绍 MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&…...

【C++新特性】右值引用

右值和右值的区别 C11 中右值可以分为两种&#xff1a;一个是将亡值&#xff08; xvalue, expiring value&#xff09;&#xff0c;另一个则是纯右值&#xff08; prvalue, PureRvalue&#xff09;&#xff1a; 纯右值&#xff1a;非引用返回的临时变量、运算表达式产生的临时变…...

信息安全基础知识(完整)

信息安全基础知识 安全策略表达模型是一种对安全需求与安全策略的抽象概念表达&#xff0c;一般分为自主访问控制模型&#xff08;HRU&#xff09;和强制访问控制模型&#xff08;BLP、Biba&#xff09;IDS基本原理是通过分析网络行为&#xff08;访问方式、访问量、与历史访问…...

QT

#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ,Gcancle(new QPushButton("取消",this)) ,EmmEdit(new QLineEdit(this)) { ui->setupUi(this);…...

双例集合(三)——双例集合的实现类之TreeMap容器类

Map接口有两个实现类&#xff0c;一个是HashMap容器类&#xff0c;另一个是TreeMap容器类。TreeMap容器类的使用在API上于HashMap容器类没有太大的区别。它们的区别主要体现在两个方面&#xff0c;一个是底层实现方式上&#xff0c;HashMap是基于Hash算法来实现的吗&#xff0c…...

[SAP ABAP] 运算符

1.算数运算符 算术运算符描述加法-减法*乘法/除法MOD取余 示例1 输出结果: 输出结果: 2.比较运算符 比较运算符描述示例 等于 A B A EQ B <> 不等于 A <> B A NE B >大于 A > B A GT B <小于 A < B A LT B >大于或等于 A > B A GE B <小…...

MSPM0G3507 ——GPIO例程讲解2——simultaneous_interrupts

主函数&#xff1a; #include "ti_msp_dl_config.h"int main(void) {SYSCFG_DL_init();/* Enable Interrupt for both GPIOA and GPIOB ports */NVIC_EnableIRQ(GPIO_SWITCHES_GPIOA_INT_IRQN); //启用SWITCHES——A的中断 NVIC_EnableIRQ(GPIO_S…...

某程序员:30岁了,老婆管钱,背着我买了50万股票,亏了20w,强制她清仓后又买了36万

“辛辛苦苦攒了几年钱&#xff0c;本想买房买车&#xff0c;结果全被老婆炒股亏掉了&#xff01;” 近日&#xff0c;一位30岁的程序员大哥在网上吐苦水&#xff0c;引发了网友们的热议。 这位程序员大哥和妻子结婚后&#xff0c;一直秉持着“男主外&#xff0c;女主内”的传统…...

Docker常见面试题整理

文章目录 1. Docker 是什么&#xff1f;它解决了什么问题&#xff1f;2. Docker 和虚拟机&#xff08;VM&#xff09;的区别是什么&#xff1f;3、Docker三个核心概念4、如何构建一个 Docker 镜像&#xff1f;5、如何将一个 Docker 容器连接到多个网络&#xff1f;6、Docker Co…...

35 - 最后一个能进入巴士的人(高频 SQL 50 题基础版)

35 - 最后一个能进入巴士的人 -- sum(weight) over(order by turn) as total,根据turn升序&#xff0c;再求前面数的和 selectperson_name from(selectperson_name,sum(weight) over(order by turn) as totalfromQueue) new_Queue wheretotal<1000 order by total desc lim…...

WPF将dll文件嵌入到exe文件中

WPF将dll文件嵌入到exe文件中 第一步&#xff1a;打开.csproj文件&#xff0c;在Import节点后添加如下代码&#xff1a; <Target Name"AfterResolveReferences"><ItemGroup><EmbeddedResource Include"(ReferenceCopyLocalPaths)" Condit…...

2024年AI+游戏赛道的公司和工具归类总结

随着人工智能技术的飞速发展,AI在游戏开发领域的应用越来越广泛。以下是对2024年AI+游戏赛道的公司和工具的归类总结,涵盖了从角色和场景设计到音频制作,再到动作捕捉和动画生成等多个方面。 2D与3D创作 2D创作工具:专注于角色和场景的平面设计,提供AI辅助的图案生成和风…...

svm和决策树基本知识以及模型评价以及模型保存

svm和决策树基本知识以及模型评价以及模型保存 文章目录 一、SVM1.1&#xff0c;常用属性函数 二、决策树2.1&#xff0c;常用属性函数2.2&#xff0c;决策树可视化2.3&#xff0c;决策树解释 3&#xff0c;模型评价3.1&#xff0c;方面一&#xff08;评价指标&#xff09;3.2&…...

C++ 79 之 自己写异常类

#include <iostream> #include <string> using namespace std;class MyOutOfRange : public exception{ // 选中exception右键 转到定义 复制一份 virtual const char* what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW 进行函数重写 public: string m_msg;M…...

如何搭建一个成功的短剧制作平台

要搭建一个成功的短剧制作平台&#xff0c;需要考虑多个方面&#xff0c;包括目标定位、技术选择、内容管理、用户体验等。 1、明确目标和定位&#xff1a; 确定你的目标受众是谁&#xff0c;他们的年龄、兴趣、消费习惯等。 明确短剧制作平台的主要定位&#xff0c;是提供原创…...

kotlin类

一、定义 1、kotlin中使用关键字class 声明类,如果一个类没有类体&#xff0c;也可以省略花括号&#xff0c; 默认为public 类型的&#xff1a; // 这段代码定义了一个公开的、不可被继承的Test类 class Test{} // 没有类体&#xff0c;可以省略花括号 class Test 底层代码&…...

android | studio的UI布局和代码调试 | UI调试 (用于找到项目源码)

网上找到一个项目&#xff0c;想快速的搞懂是怎么实现的&#xff0c;搞了半天发现原来android都升级到Jetpack Compose了&#xff0c;然后去找源码挺不容易的&#xff0c;摸索中发现了这个调试的方法&#xff0c;还可以。 https://developer.android.com/studio/debug/layout-i…...

LangChain实战技巧之六:一起玩转config(上篇)——ConfigurableField

简介 Config 包含两大类内容&#xff0c; ConfigurableField 可配置的字段 configurable_alternatives 可配置的替代方案 分别使用两篇文章来给大家介绍&#xff0c;本篇先介绍ConfigurableField 常规介绍 一些资料会这样介绍 model_spec model.configurable_fields(model…...

扫码称重上位机

目录 一 设计原型 二 后台代码 一 设计原型 模拟工具: 二 后台代码 主程序&#xff1a; using System.IO.Ports; using System.Net; using System.Net.Sockets; using System.Text;namespace 扫码称重上位机 {public partial class Form1 : Form{public Form1(){Initialize…...

操作系统入门 -- 进程的通信方式

操作系统入门 – 进程的通信方式 1.什么是进程通信 1.1 定义 进程通信就是在不同进程之间交换信息。在之前文章中可以了解到&#xff0c;进程之间相互独立&#xff0c;一般不可能互相访问。因此进程之间若需要通信&#xff0c;则需要一个所有进程都认可的共享空间&#xff0…...