【C++】类与对象
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。
struct Stack
{// 成员函数void Init(int defaultCapacity = 4){a = (int*)malloc(sizeof(int) * capacity); if (nullptr == a){perror("malloc申请空间失败");return;}capacity = defaultCapacity; top = 0;}void Push(int x){// 扩容 a[top++] = x;}void Destroy(){free(a); a = nullptr; top = capacity;}//...// 成员变量int* a; int top;int capacity;
};int main()
{Stack s; s.Init(10); s. Push(1); s.Push(2); s.Push(3); cout << s. Top () << endl; s.Destroy(); return 0;
}
1、类的定义
class className
{// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{ } 中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加 类名 ::
成员变量命名规则建议:
// 我们看看这个函数,是不是很僵硬?
class Date
{public:void Init(int year){// 这里的year到底是成员变量,还是函数形参? year = year;}private:int year;
};// 所以一般都建议这样
class Date
{public:void Init(int year){_year = year;}private:int _year;
};// 或者这样
class Date
{public:void Init(int year){mYear = year;}private:int mYear;
};// 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。
2、类的访问限定符及封装
访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
C++中 struct 和 class 的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class 定义的类默认访问权限是private。
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
3、类的实例化
用 类 类型创建对象的过程,称为类的实例化
1. 类是对对象进行描述的,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
4、类和对象的大小
类和对象的存储方式:
在定义一个对象后,对象的存储的是它的数据成员,成员函数没有在对象内部。
同一个类和它实例化后的对象,sizeof(类)和 sizeof(对象)的结果是相同的。
类有内存对齐的现象:
空类和无数据成员的类的大小:
// 类中仅有成员函数
class A2
{ public:void f2() {}
};// 类中什么都没有 --- 空类
class A3
{};
sizeof(A2)和 sizeof(A3)的结果都是 1 ,为什么?
没有成员变量的类对象,需要 1 字节的原因是占位,表示对象存在(如果一个对象的大小是 0 字节,那么对象取地址怎么办呢?)
5、this 指针
同一个类定义的不同对象,各自调用它们的成员函数时,为什么每个对象的成员函数的接收到的形参都是各自对象的实参,明明都是调用的同一个函数。
某类有这样的一个成员函数:
void Print()
{cout << _ year << "-" <<_ month << "-" <<_ day << endl;
}
经过编译器处理后,它变成了这样:
void Print(Date* this)
{cout << this->_ year << "-" <this->_ month << "-" <<this->_ day << endl;
}
this 是指向对象的指针,哪个对象调用了 Print 函数,this 指针就指向谁。
1、this 指针不能在实参和形参中显式使用,但可以在函数内部显式使用。
2、this 指针是 类名* const 类型的,在函数中不允许修改它的指向。
3、this是形参,所以this指针是跟普通参数一样存储在函数调用的栈帧里面。
// 1. 下面程序编译运行结果是?
A、编译报错
B、运行崩溃
C、正常运行
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr; p->Print();return 0;
}
答案是 C。
p调用Print,不会发生解引用,因为Print的地址不在对象中。p会作为实参传递给this指针。在Print函数中,没有对 this 进行解引用操作。
如果将 Print 函数该为下面的样子,答案就是 B:
void Print()
{cout << _a << endl;
}
现在 Print 函数存在对 this 的解引用,而 this 接收的实参是 p,p 是 nullptr。
有人说 p->Print(); 存在对 p 的解引用操作,但是从汇编代码来看,p->Print(); 只是将 p 作为实参传递给 this。
6、类的六个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。用户显示实现默认成员函数时,不能在类外实现,否则会与编译器默认生成的成员函数冲突,可以在类中声明,在类外实现。
构造函数和析构函数是为了解决频繁的初始化和销毁工作,减少因为忘记初始化和销毁带来的问题。
构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。其特征如下:
1. 函数名与类名相同。
2. 无返回值。(不需要写 void)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。全缺省的构造函数与无参构造函数构成重载,但调用时存在歧义。5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6、编译器默认生成的构造函数对基本(int、double等等)类型不做处理,对自定义类型(class、struct)会去调用它的默认构造函数。(有些编译器对一个类默认生成的构造函数,如果该类有自定义数据成员和基本类型成员时,也会将基本类型初始化为 0,但不是所有的编译器都会这样做)
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都可以认为是默认构造函数。默认构造函数就是不传参就可以调用的函数。
示例:
class A
{
public:// 有参构造函数A(int a){_a = a;}// 无参构造函数A(){_a = 1;}void Print(){cout << "Print()" << endl;}
private:int _a;
};int main()
{A a(10);// _a 的初始值是 10A a; // _a 的初始值是 1;return 0;
}
注意:调用自己写的无参构造函数不能写成这样:
A a(); // error
原因:编译器会误以为 A a();是函数声明:函数名是 a,没有参数,返回值类型是 A 类。
而应该这样:
A a;
什么时候要写构造函数?
1、一般情况下,一个类有基本类型成员,就需要自己写构造函数。
2、如果类中全部都是自定义类型成员,可以考虑使用编译器自己生成的默认构造函数。
3、类中的基本类型成员都有缺省值,这时可以不写构造函数。
类中的内置类型成员可以给缺省值
C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。如:
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a = 1;
};
在创建 A 类对象时,_a 的默认值是 1。但是如果我们自己写了构造函数,并在定义对象时初始化,该缺省值就没有用了。
初始化列表:
除了在构造函数的函数体内对成员变量赋值的方式来初始化对象,还有一种初始化对象的方式,即初始化列表。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个“成员变量”后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day):_ year(year) ,_month(month) ,_day(day)
{}
private: int _year; int _month; int _day;
};
注意:
1. 每个成员变量在初始化列表中只能出现一次(也可以不出现)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:(而不能在构造函数的函数体内初始化)
1)引用成员变量
2)const成员变量引用成员变量和const成员变量的共同特征是必须在定义的时候初始化(const int a = 10,int& b = a)而一个普通变量(如 int 类型的变量),在定义时可以不初始化(int a;)。在定义对象时,引用成员变量和const成员变量随之定义,如果在构造函数的函数体内通过赋值的方式来初始化这些成员的话,就不满足在定义的时候初始化,初始化列表就是为了解决这样的问题。
3)自定义类型成员(且该类没有默认构造函数时)3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序。
class A { public:A(int a):_a1(a),_a2(_a1){}void Print() {cout <<_ al << " " <<_ a2 << endl;} private:int _a2; int _a1; }int main() {A a(1);a.Print();return 0; }
程序输出:1 随机值
原因:先定义 _a2,再定义 _a1,所以初始化列表的执行顺序是:先执行 _a2(_a1) 这时 _a1 还是随机值,赋值给了_a2,再执行_a1(a)。
初始化列表不能完全代替构造函数,它是构造函数的一部分:
class Stack
{
public:Stack(int capacity = 10)//初始化列表:_ a((int*)malloc(capacity * sizeof(int))) ,_top(0) ,_capacity(capacity){//初始化列表不能完成的任务if (nullptr == _ a){perror("malloc申请空间失败”);exit(-1);}// 要求数组初始化一下memset(_a, 0, sizeof(int) * capacity);}
private:int* _ a;int _top; int _capacity;
}
动态开辟二维数组:
class AA
{
public:AA(int row = 10, int col = 5):_ row(row),_col(col){_aa = (int**)malloc(sizeof(int*) * row); // _aa 数组是指针数组,每个数组元素都指向一个 // 一维数组for (int i = 0; i < row; i++){aa[i] = (int*)malloc(sizeof(int) * col);}
private:int ** _ aa; int _row; int _col;
};
析构函数
概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型(不加 void)。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。5、编译器默认生成的析构函数对基本(int、double等等)类型不做处理,对自定义类型(class、struct)会去调用它的析构函数。
6、对象析构的顺序满足“后定义的先析构”。
什么时候写析构函数
1、一般情况下,有动态申请的堆上的内存空间,就需要写析构函数释放内存空间
2、没有动态申请的的堆上的内存空间,不需要写析构函数
3、需要释放空间的成员都是自定义类型,不需要写析构函数
构造函数和析构函数的优点:构造函数和析构函数是为了解决频繁的初始化和销毁工作,减少因为忘记初始化和销毁带来的问题。
拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
类名( const 类名& 对象名){ }
特征
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。(调用拷贝构造函数要传递参数,传递参数要拷贝实参给形参,又要调用拷贝构造函数,又要传递参数,传递参数要拷贝实参给形参,又要调用拷贝构造函数...)
3. 若未显式定义,编译器会生成默认的拷贝构造函数。对基本类型完成值拷贝(浅拷贝),对自定义类型,调用它的拷贝构造函数。
示例:
在定义 Date 类时,有如下拷贝构造函数:
void Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
使用拷贝构造函数:
Date d1(d2);
定义日期类对象 d1,并用 d2(已存在)初始化。
C++ 规定,基本类型直接拷贝,自定义类型必须调用拷贝构造函数来拷贝。如果函数的形参是自定义类型的引用,不再调用拷贝构造函数,且实参与形参之间没有发生拷贝。
若类中有指针变量成员,该类的对象使用默认拷贝函数拷贝时,拷贝后两个对象的指针成员都指向同一块内存空间(这不是我们期望的拷贝结果,我们期望的是两个指针成员指向两块内存空间,这两块内存空间的存储内容一样),这块内存空间就会被析构两次,程序会崩溃。
与赋值重载函数的区别
// 已经存在的两个对象之间复制拷贝 -- 运算符重载函数
Date d1(2024,5,1);
Date d2(2024,6,1);
d1 = d2;// 用一个已经存在的对象初始化另一个对象 -- 构造函数
Date d1(2024,5,1);Date d3(d1);
//或
Date d3 = d1;
注意:不是只要有 = 就要调用运算符重载函数,如:Date d4 = d1;d1 是已经定义并初始化的对象,这时是调用的拷贝构造函数
赋值重载函数
对 = 进行运算符重载,实现两个同类的对象之间的赋值。
class Date
{
private:int _year;int _month;int _day;
public:void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
}
如有 d1 = d2;则调用 d1 的 operator= 函数。以上代码存在一个问题,就是对象之间不能连续赋值,比如:d1 = d2 = d3;这是因为 operator= 函数的返回值是 void(d2 = d3,这个表达式的值是 void)。对 operator= 函数做如下改进:
Date& operator=(const Date& d)
{if( this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
1、*this 出了 operator= 函数后还存在,所以可以使用引用返回,减少对象的拷贝(函数返回值要调用拷贝构造函数拷贝到寄存器,再由寄存器将值赋给表达式)
2、为了可以检测自己给自己赋值,可以加一个 if 判断。
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。默认赋值运算符重载对内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
取地址运算符的重载函数
此函数如果我们不写,编译器会默认生成以下函数:
Date* operator&()
{return this;
}const Date* operator&()const
{return this;
}
这两个函数构成了函数重载:第一个 operaor& 函数的形参是 Date* this,第二个函数的形参是 const Date* this。
有时候,我们不想让别人获取到非常对象的地址,而常对象的地址可以获取,就可以这样做:
Date* operator&()
{return nullptr;
}const Date* operator&()const
{return this;
}
7、运算符重载
有以下一个类:
class Date
{
public:int _year;int _month;int _day;
}
如果要比较两个日期类对象的先后,一般是写一个函数,通过函数调用返回布尔值来判断:
bool less(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;}return false;
}
能否使用 > 来直接判断呢?比如:d1 > d2,只要将函数名该为:operator< 即可。
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;}return false;
}
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:
返回值类型 operator操作符(参数列表)
注意:
● 不能通过连接其他符号来创建新的操作符:比如 operator@
● 重载操作符必须有一个类类型参数,重载操作符有几个操作数,就有几个参数。
● 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
● 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的 this(以上代码仍有问题:_year、_month、_day 一般是私有的数据成员,在类外不能直接访问私有成员,一个解决方法是将 operator< 函数作为 Date 类的成员函数:)
调用上面的函数时,可以这样调用:d1 > d2,也可以这样:d1.opreator<(d2)。
● .*(点和星号)::(域作用限定符)sizeof (求大小) ?:(三目)注意这 5个运算符不能重载。
实现运算符重载的技巧:
1、先实现 operator<(上面已实现) 和 operator== :
bool operator==(const Date& d)
{return _year == d._year&& _month == d._month;&& _day == d._day;
}
2、有了 operator< 和 operator==,那么 operator<= 和 operator >=只需:
bool Date::operator<= (const Date& x)
{return *this < x | | *this == X;
}
bool Date::operator>= (const Date& x)
{return !(*this < x);
}
3、有了 operator<= ,operator> 只需:
bool Date::operator>(const Date& x)
{return !(*this <= x);
}
实现 Date 类 加上天数的 + 重载:
Date& Date::operator+(int day)
{day += day; while(_day > GetMonthDay(_year, _month)){day -= GetMonthDay(_year, _month); ++_month;if(_month == 13){++_year; month = 1;}}return *this;
}
这样,Date 类的对象就能计算加上某个天数后的年月日,比如:
int main()
{Date d1(2025,5,1);d1 + 100;d1.print();return 0;
}
代码有些失误,实际上上面代码实现的是 operator+=,而不是 operator+,比如 int i = 0;i + 10;i 的值并没有改变。问题出在上面代码对 Date 类型直接 + 了 。
Date& Date::operator+=(int day)
{day += day; while(_day > GetMonthDay(_year, _month)){day -= GetMonthDay(_year, _month); ++_month;if(_month == 13){++_year; month = 1;}}return *this;
}
+= 也有返回值的意义是实现连续的 +=,比如 i += j += 10。重复使用 operator+= 实现 + :
Date Date::operator+(int day)
{Date tmp(*this);tmp._day += day; return tmp;
}
1、使用拷贝构造函数来初始化 tmp,不能对 *this 直接 +
2、tmp 出了函数就被销毁,所以不能用引用返回。
3、可以用 operator+ 反过来实现 operator+=,但不如上面的实现方法,因为上面的实现方法只创建了两个对象,而这种方法创建了四个对象。推荐先实现 += ,再实现 +,减法类似
4、以上代码不能应对 d1 += -100 这种情况,在实现 -= 和 - 后,在 operator+ 中加 if 判断,operator- 也是。
前置++和后置++:
我们已经知道前置++和后置++的规则:
Date d1(2025,5,1)d1++;
++d1;
//d1 都要 +1,++d1 返回 +1 后的 d1,d1++ 返回 +1 前的 d1
那具体怎么实现呢?
operator++ 默认是前置 ++:
// 前置++
Date& Date::operator++()
{*this += 1; return *this;
}
+= 已重载(见上文)
那后置 ++ 如何实现呢?
// 后置++
//增加这个int参数不是为了接收具体的值,仅仅是占位,跟前置++构成函数重载
Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}
对类对象的输入与输出的操作符重载
流插入操作符 << 是 C++ 新增的操作符,在库里面已经实现了它对内置类型的重载:
对于自定义类型,比如 Date 类型,直接使用 cout<< d1 << endl;是不行的(d1 是 Date 类的对象),需要对 << 进行运算符重载。
void Date::operator<<(ostream& out)
{out<<_ year<<"年"<<_ month<<"月"<<_ day<<"日"<<endl;
}
但是如果我们用 cout << d1 ;来使用重载后的操作符,会发现程序编译不通过,其实要这样写才能使用重载后的操作符:d1 << cout;这违背了我们的使用习惯。所以 << 的重载函数不能写成 Date 类的成员函数,而应该写成全局函数。
void operator<<(ostream& out,const Date d)
{out<<d._ year<<"年"<<d._ month<<"月"<<d._ day<<"日"<<endl;
}
新的问题由出现了:operator<< 是全局函数,无法直接访问 Date 类对象的数据成员。有两个解决办法:
1、在 Date 类里再增加这些成员函数:
int Getyear()
{return _year;
}
operator<< 函数写成:
void operator<<(ostream& out,const Date d)
{out<<d.Getyear()<<"年"<<d.Getmonth()<<"月"<<d.Getday()<<"日"<<endl;
}
2、将 operator<< 作为 Date 类的友元函数。
以上代码无法实现连续打印:cout << d1 << d2 << d3;解决办法是将 operator<< 的返回值设为 ostream 类:
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;
}
8、const 成员函数
常对象只能调用常成员函数
d2 不能调用 Print 函数是因为 d2 是常对象,常对象在调用非常函数时,传递给 this 指针是 const Date* 类型的地址,权限被放大。
怎么让 d2 也可以调用 Print 函数呢?当然是将 this 指针转换成 const Date* 类型的指针变量,由于不能在实参和形参中显式使用 this 指针,所以,用 const 修饰 this 指针的方法是:
void Print() const
const 不是修饰 Print 函数的,是修饰 this 指针的。
哪些成员函数可以加 const
如果一个成员函数没有修改对象数据成员的打算(单纯打印数据、对 + 、-、*、/ 、>、<等运算符重载的函数等等),就可以加 const。如果成员函数可能修改对象的数据成员,就不能加 const,否则函数的功能不能实现。如果要加 const,则在声明和定义都要加。
请思考下面的几个问题:
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
9、 static 成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
静态成员变量:
1、不属于类的每个对象,它属于类,即被类的每个对象所共享。
2、它必须在类外定义,定义时不加 static,类中只是声明(没有为什么,就是规定)。
3、它通常是私有的,要配合使用静态成员函数访问。
4、它存储在静态区
静态成员函数:
1、通常与静态成员变量同时出现,用于获取静态成员变量,
2、它没有this指针,不能访问对象的成员,
3、它通过类名和域访问限定符就可以访问
4、类中其他非静态的成员函数可以调用静态成员函数,但静态成员函数不能调用类的非静态函数,因为静态成员函数没有this指针
例子一:实现一个类,计算程序中创建出了多少个类对象。
class A
{
public: A() { ++_scount; } A(const A& t) { ++_scount; } ~A() { --_scount; } static int GetACount () { return _scount; }
private: static int _scount;
};
例子二:实现一个类,这个类只能在栈或堆上创建对象。
class A
{
public:static A GetStackObj (){A aa; return aa;}static A* GetHeapObj (){return new A;}
private:A(){}int _a1 = 1; int _a2 = 2;
};
上面的 A 类中,构造函数被 private 访问限定符限定,如果我们直接创建一个 A 类的对象,是不行的,必须调用 GetStackObj () 函数或 GetHeapObj () 函数来创建 A 类的对象,但是如果要调用GetStackObj () 函数或 GetHeapObj () 函数,又必须有一个 A 类的对象,使用静态函数就可以解决这个问题,所以将GetStackObj () 函数和GetHeapObj () 函数声明为静态函数。
10、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
问题:现在尝试去重载operator << ,然后发现没办法将operator << 重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator << 重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
class Date
{//友元声明friend ostream& operator<<(ostream& out,const Date d);
public:
Date(int year, int month, int day):_ year(year) ,_month(month) ,_day(day)
{}
private: int _year; int _month; int _day;
};ostream& operator<<(ostream& out,const Date d)
{out<<d._ year<<"年"<<d._ month<<"月"<<d._ day<<"日"<<endl;return out;
}
注意:
● 友元函数可访问类的私有和保护成员,但不是类的成员函数
● 友元函数不能用const修饰
●友元函数可以在类定义的任何地方声明,不受类访问限定符限制
● 一个函数可以是多个类的友元函数
●友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
· 友元关系是单向的,不具有交换性。
在A类中声明B类为其友元类,那么可以在B类的成员函数中直接访问A类的私有成员变量,但想在A类中访问B类中私有的成员变量则不行。
· 友元关系不能传递
如果C是B的友元,B是A的友元,则不能说明C时A的友元。
相关文章:
【C++】类与对象
C语言结构体中只能定义变量,在C中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构中,用C语言方式实现的栈,结构体中只能定义变量;现在以C方式实现,会发现struct中也可以定义函数。 struct Stack {// 成员函数void Init(int defaultCapacity 4){a (int*)mall…...
mac M芯片运行docker-desktop异常问题
虽然mac已经迭代到m4了,但官方的docker-desktop运行仍然有问题,包括但不限于: 命令行docker找不到docker-desk打不开docker-desktop闪退容器起不来 尝试不同版本后,看到了其他可以在mac跑docker的开源方法,更简单、轻…...
5G 技术在智能制造中的应用:加速工业革命的新引擎
5G 技术在智能制造中的应用:加速工业革命的新引擎 在过去几十年里,制造业经历了从机械化到自动化,再到如今的智能化变革。而 5G 技术的出现,不仅是一次通信技术的升级,更是为 智能制造 注入了新的动力。从 智能工厂、工业物联网(IIoT) 到 远程控制与数据智能分析,5G 正…...
数据治理域——数据同步设计
摘要 本文主要介绍了数据同步的多种方式,包括直连同步、数据文件同步和数据库日志解析同步。每种方式都有其适用场景、技术特点、优缺点以及适用的数据类型和实时性要求。文章还详细探讨了数据直连同步的特点、工作原理、优点、缺点、适用场景等,并对数…...
系统架构设计师案例分析题——web篇
软考高项系统架构设计师,其中的科二案例分析题为5选3,总分75达到45分即合格。本贴来归纳web设计题目中常见的知识点即细节: 目录 一.核心知识 1.常见英文名词 2.私有云 3.面向对象三模型 4.计网相关——TCP和UDP的差异 5.MQTT和AMQP协…...
FC7300 SPI MCAL配置引导
一、MCU 组件 - 配置SPI时钟 MCU中配置的SPI输入时钟频率至少应大于2倍的SPI组件中配置的外设波特率。SPI时钟配置为30MHz 二、SPI 组件 - General Spi Level Delivered: 0 级:仅简单同步行为1 级:基本异步行为,通过中断实现2 级:增强型行为,通过轮询实现根据AUTOSAR SPI…...
【记录】Windows|竖屏怎么调整分辨率使横竖双屏互动鼠标丝滑
本文版本:Windows11,记录一下,我最后调整的比较舒适的分辨率是800*1280。 文章目录 第一步 回到桌面第二步 右键桌面第三步 设置横屏为主显示器第四步 调整分辨率使之符合你的需求第五步 勾选轻松在显示器之间移动光标第六步 拖动屏幕符合物理…...
hghac和hgproxy版本升级相关操作和注意事项
文章目录 环境文档用途详细信息 环境 系统平台:N/A 版本:4.5.6,4.5.7,4.5.8 文档用途 本文档用于高可用集群环境中hghac组件和hgproxy组件替换和升级操作 详细信息 1.关闭服务 所有数据节点都执行 1、关闭hgproxy服务 [roothgdb01 tools]# system…...
【超分辨率专题】一种考量视频编码比特率优化能力的超分辨率基准
这是一个Benchmark,超分辨率视频编码(2024) 专题介绍一、研究背景二、相关工作2.1 SR的发展2.2 SR benchmark的发展 三、Benchmark细节3.1 数据集制作3.2 模型选择3.3 编解码器和压缩标准选择3.4 Benchmark pipeline3.5 质量评估和主观评价研…...
操作系统之进程和线程听课笔记
计算机的上电运行就是构建进程树,进程调度就是在进程树节点进程进行切换 进程间通信的好处 经典模型 生产者和消费者 进程和线程的区别 线程引入带来的问题线程的优势 由于unix70年代产生,90年代有线程,当时数据库系统操作需要线程,操作系统没有来得及重造,出现了用户态线…...
Mac安装Navicat16
我的电脑用的是M3芯片,然后在安装的时候也踩了很多的坑 先分享一下链接 通过网盘分享的文件:Navicat Premium v16.2.dmg 链接: https://pan.baidu.com/s/1ENLtU7VLCvzntLKqSyFiqg?pwd1234 提取码: 1234 其实按理说用navicat17也是可以的 首先下载完成后…...
表的设计、聚合函数
目录 1、表的设计 1.1、一对一 1.2、一对多 1.3、多对多 2、插入查询结果 3、聚合查询 3.1、聚合函数 3.2、GROUP BY子句 1、表的设计 根据实际的需求场景,明确当前要创建几个表,每个表什么样子,这些表之间是否存在一定联系 1. 梳理…...
React学习———React Router
React Router React Router 是 React 应用中用于管理路由的流行库,它允许你在单页应用(SPA)中实现导航和页面切换而无需重新加载页面。 安装 npm install react-router-dom核心组件 <BrowserRouter> 使用HTML5的历史记录API&#…...
【前端】[vue3] [uni-app]使用 vantUI 框架
npm 安装: npm i vant/weapp -S --productionmain.js 中挂载 App.vue 引入 vantUI 样式 完成:...
upload-labs通关笔记-第8关 文件上传之点绕过
目录 一、点绕过原理 二、deldot()函数 三、源码分析 四、渗透实战 1、构建脚本test8.php 2、打开靶场 3、bp开启拦截 4、点击上传 5、bp拦截 6、后缀名增加点 7、发包并获取脚本地址 8、访问脚本 本文通过《upload-labs靶场通关笔记系列》来进行upload-labs靶场的渗…...
XML简要介绍
实际上现在的Java Web项目中更多的是基于springboot开发的,所以很少再使用xml去配置项目。所以我们的目的就是尽可能快速的去了解如何读懂和使用xml文件,对于DTD,XMLSchema这类约束的学习可以放松,主要是确保自己知道这里面的大致…...
我设计的一个安全的 web 系统用户密码管理流程
作为一名有多年经验的前端,在刚开始学习web后端的时候,就对如何设计一个安全的 web 系统用户密码管理流程有很多疑问。之前自己也实践过几种方法,但一直觉得不是十分安全。 我们知道,用户在注册或登录界面填写的密码是明文的&…...
事件驱动架构:从传统服务到实时响应的IT新风潮
文章目录 事件驱动架构的本质:从请求到事件的范式转变在EDA中: 事件驱动架构的演进:从消息队列到云原生标配核心技术:事件驱动架构的基石与工具链1. 消息队列:事件传递的枢纽2. 消费者:异步处理3. 事件总线…...
YOLOv11改进 | Neck篇 | 轻量化跨尺度跨通道融合颈部CCFM助力YOLOv11有效涨点
YOLOv11改进 | Neck篇 | 轻量化跨尺度跨通道融合颈部CCFM助力YOLOv11有效涨点 引言 在目标检测领域,YOLO系列算法因其卓越的速度-精度平衡而广受欢迎。YOLOv11作为该系列的最新演进版本,在Neck部分引入了创新的跨尺度跨通道融合模块(CCFM, Cross-scale…...
Unity:场景管理系统 —— SceneManagement 模块
目录 🎬 什么是 Scene(场景)? Unity 项目中的 Scene 通常负责什么? 🌍 一个 Scene 包含哪些元素? Scene 的切换与管理 📁 如何创建与管理 Scenes? 什么是Scene Man…...
官方 Elasticsearch SQL NLPChina Elasticsearch SQL
官方的可以在kibana 控制台上进行查询: POST /_sql { “query”: “SELECT client_ip, status FROM logs-2024-05 WHERE status 500” } NLPChina Elasticsearch SQL就无法以在kibana 控制台上进行查询,但是可以使用postman接口进行查询:...
Kubernetes 1.28 无 Docker 运行时环境下的容器化构建实践:Kaniko + Jenkins 全链路详解
背景说明 随着 Kubernetes 1.28 正式弃用 Docker 作为默认容器运行时(CRI 规范演进),传统的 docker build 方式已无法直接在集群内运行。Kaniko 作为 Google 开源的容器镜像构建工具,凭借其无需特权容器、兼容 OCI 标准的特性&am…...
【Linux】序列化与反序列化、会话与进程组、守护进程
一.序列化和反序列化 协议其实就是结构化的数据。但是再网络通信中,我们不直接发送结构化的数据给对方。我们一般会将结构化的数据序列化成字符串/字节流,然后通过网络在发送出去。而接收方收到之后,要对收到的字符串/流式数据进行反序列化&…...
Python机器学习笔记(二十五、算法链与管道)
对于许多机器学习算法,特定数据表示非常重要。首先对数据进行缩放,然后手动合并特征,再利用无监督机器学习来学习特征。因此,大多数机器学习应用不仅需要应用单个算法,而且还需要将许多不同的处理步骤和机器学习模型链接在一起。Pipeline类可以用来简化构建变换和模型链的…...
自媒体工作室如何矩阵?自媒体矩阵养号策略
一、自媒体工作室矩阵搭建方法 1.纵向矩阵:在主流平台都开设账号,覆盖不同用户触达场景。 短视频:抖音、快手、视频号(侧重私域沉淀) 2.主账号导流:通过关联账号、评论区跳转链接实现流量互通 本地生活…...
Pywinauto:轻松实现Windows桌面自动化实战
你是否厌倦了每天重复点击软件界面的枯燥操作?是否希望能像自动化网页那样,轻松控制桌面程序?在自动化测试逐渐扩展到客户端桌面的今天,你还不知道 pywinauto,就真的落后了! 手动测试Windows桌面应用&…...
告别传统的防抖机制,提交按钮的新时代来临
目录 背景 目标 核心代码 样式定义:让图标居中、响应父级颜色 SVG 图标:轻量、无依赖的 loading 图标 指令注册:全局注册 v-bLoading DOM 操作:添加与清除 loading 图标 1. 添加 loading 图标 2. 清除 loading 图标 动画…...
【Linux】Linux安装并配置MongoDB
目录 1.添加仓库 2.安装 MongoDB 包 3.启动 MongoDB 服务 4. 验证安装 5.配置 5.1.进入无认证模式 5.2.1创建用户 5.2.2.开启认证 5.2.3重启 5.2.4.登录 6.端口变更 7.卸载 7.1.停止 MongoDB 服务 7.2.禁用 MongoDB 开机自启动 7.3.卸载 MongoDB 包 7.4.删除数…...
PT2031K单触控单输出触摸IC
1.产品概述 ● PT2031K是一款电容式触摸控制ASIC,支持单通道触摸输入和单路同步开关输出。适用于雾化器、车载用品、电子玩具、消费类电子产品等领域,具有低功耗、高抗干扰、宽工作电压范围的突出优势。 2.主要特性 ● 工作电压范围:2.4~5.5…...
MySQL 与 FastAPI 交互教程
目录 1. 使用 Docker 启动 MySQL2. 创建 FastAPI 应用安装必要的依赖创建项目结构创建数据库连接模块创建数据模型创建 Pydantic 模型(用于请求和响应)创建主应用 3. 运行和测试应用启动应用访问 API 文档 4. 测试 API 端点创建用户获取所有用户获取特定…...
分布式 ID 生成的五种方法:优缺点与适用场景
0.简介 在分布式系统中,生成全局唯一的id是一个常见的需求。由于分布式系统的特性(多节点,网络分区,时钟不同步等),传统的单机ID生成方式不再适用,所以一些分布式生成方式应运而生,…...
ES(Elasticsearch)的应用与代码示例
Elasticsearch应用与代码示例技术文章大纲 一、引言 Elasticsearch在现代化应用中的核心作用典型应用场景分析(日志分析/全文检索/数据聚合) 二、环境准备(前提条件) Elasticsearch 8.x集群部署要点IK中文分词插件配置指南Ingest Attachment插件安装…...
MinIO 开源的分布式文件服务器
如下是java代码调用MinIO的SDK实现文件的上传,并获取url <dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version> <!-- 你可以选择4.8.1或更高版本 --></dependenc…...
蓝牙AVRCP协议概述
AVRCP(Audio/Video Remote Control Profile)定义了蓝牙设备和 audio/video 控制功能通信的特 点和过程,另用于远程控制音视频设备,底层传输基于 AVCTP 传输协议。该 Profile 定义了AV/C 数字命令控制集。命令和信息通过 AVCTP(Audio/Video Control Trans…...
【全网首发】解决coze工作流批量上传excel数据文档数据重复的问题
注意:目前方法将基于前一章批量数据库导入的修改!!!!请先阅读上篇文章的操作。抄袭注明来源 背景 上一节说的方法可以批量导入文件到数据库,但是无法解决已经上传的条目更新问题。简单来说,不…...
Hue面试内容整理-Hue 架构与前后端通信
Cloudera Hue 是一个基于 Web 的 SQL 助手,旨在为数据分析师和工程师提供统一的界面,以便与 Hadoop 生态系统中的各个组件(如 Hive、Impala、HDFS 等)进行交互。其架构设计强调前后端的分离与高效通信,确保系统的可扩展性和可维护性。以下是 Hue 架构及其前后端通信机制的…...
【八股战神篇】Java高频基础面试题
1 面向对象编程有哪些特性? 面向对象编程(Object-Oriented Programming,简称 OOP)是一种以对象为核心的编程范式,它通过模拟现实世界中的事物及其关系来组织代码。OOP 具有三大核心特性:封装、继承、多态。…...
matlab建立整车模型,求汽车的平顺性
在MATLAB中建立整车模型评估汽车平顺性,通常采用多自由度振动模型。以下是基于四分之一车模型的详细步骤和代码示例,可扩展至整车模型。 1. 四分之一车模型(简化版) 模型描述 自由度:2个(车身垂直位移 z2…...
在Linux服务器上部署Jupyter Notebook并实现ssh无密码远程访问
Jupyter notebook版本7.4.2(这个版本AI提示我Jupyter7(底层是 jupyter_server 2.x) 服务器开启服务 安装Jupyter notebook 7.4.2成功后,终端输入 jupyter notebook --generate-config 这将在 ~/.jupyter/ 目录下生成 jupyter_…...
C#数组与集合
🧠 一、数组(Array) 1. 定义和初始化数组 // 定义并初始化数组 int[] numbers new int[5]; // 默认值为 0// 声明并赋值 string[] names { "Tom", "Jerry", "Bob" };// 使用 new 初始化 double[] scores …...
服务器内部可以访问外部网络,docker内部无法访问外部网络,只能docker内部访问
要通过 iptables 将容器中的特定端口请求转发到特定服务器,你需要设置 DNAT(目标地址转换)规则。以下是详细步骤: 假设场景 容器端口: 8080(容器内服务监听的端口)目标服务器: 192.168.1.100(请…...
mathematics-2024《Graph Convolutional Network for Image Restoration: A Survey》
推荐深蓝学院的《深度神经网络加速:cuDNN 与 TensorRT》,课程面向就业,细致讲解CUDA运算的理论支撑与实践,学完可以系统化掌握CUDA基础编程知识以及TensorRT实战,并且能够利用GPU开发高性能、高并发的软件系统…...
ssti刷刷刷
[NewStarCTF 公开赛赛道]BabySSTI_One 测试发现过滤关键字,但是特殊符号中括号、双引号、点都能用 可以考虑拼接或者编码,这里使用拼接 ?name{{()["__cla"~"ss__"]}}?name{{()["__cla"~"ss__"]["__ba&…...
Zephyr OS Nordic芯片的Flash 操作
目录 概述 1. 软硬件环境 1.1 软件开发环境 1.2 硬件环境 2 Flash操作库函数 2.1 nRF52832的Flash 2.2 Nordic 特有的 Flash 操作 2.2.1 nrfx_nvmc_bytes_write 函数 2.2.2 nrfx_nvmc_page_erase函数 2.2.3 nrfx_nvmc_write_done_check 函数 3 操作Flash的接口函数…...
傅里叶变换实战:图像去噪与边缘提取
傅里叶变换在图像处理中的应用与实践详解(超详细教程实战代码) 🚀 本文从零开始详解傅里叶变换在图像处理中的应用,手把手教你实现图像去噪与边缘提取!全文配套Python代码,新手也能轻松上手! 一…...
go-中间件的使用
中间件介绍 Gin框架允许开发者在处理请求的过程中加入用户自己的钩子(Hook)函数这个钩子函数就是中间件,中间件适合处理一些公共的业务逻辑比如登录认证,权限校验,数据分页,记录日志,耗时统计 1.定义全局中间件 pac…...
昇腾NPU环境搭建
如果进入服务器输入npu-smi info可以看到npu情况,请直接跳转第三步 STEP1: 服务器安装依赖 sudo yum install -y gcc gcc-c make cmake unzip zlib-devel libffi-devel openssl-devel pciutils net-tools sqlite-devel lapack-devel gcc-gfortran python3-develyu…...
【HTML5学习笔记2】html标签(下)
1表格标签 1.1表格作用 显示数据 1.2基本语法 <table><tr> 一行<td>单元格1</td></tr> </table> 1.3表头单元格标签 表头单元格会加粗并且居中 <table><tr> 一行<th>单元格1</th></tr> </table&g…...
开源轻量级地图解决方案leaflet
Leaflet 地图:开源轻量级地图解决方案 Leaflet 是一个开源的 JavaScript 库,用于在网页中嵌入交互式地图。它以轻量级、灵活性和易用性著称,适用于需要快速集成地图功能的项目。以下是关于 Leaflet 的详细介绍和使用指南。 1. Leaflet 的核心…...
LLM学习笔记(六)线性代数
公式速查表 1. 向量与矩阵:表示、转换与知识存储的基础 向量表示 (Vectors): 语义的载体 在LLM中,向量 x ∈ R d \mathbf{x}\in\mathbb{R}^d x∈Rd 是信息的基本单元,承载着丰富的语义信息: 词嵌入向量 (Word Embeddings)&am…...