【C++】类和对象(下)

news2025/4/3 21:58:47

​🌠 作者:@阿亮joy.
🎆专栏:《吃透西嘎嘎》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉再谈构造函数👈
      • 构造函数体赋值
      • 初始化列表
      • explicit 关键字
    • 👉static 成员👈
      • 概念
      • 特性
    • 👉友元👈
      • 友元函数
      • 友元类
    • 👉内部类👈
      • 概念
      • 特性
    • 👉匿名对象👈
    • 👉拷贝对象时的一些编译器优化👈
    • 👉再次理解类和对象👈
    • 👉总结👈

👉再谈构造函数👈

构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

现在我们大概知道了初始化列表的大致玩法,那我们把之间写的栈Stack也用初始化列表来初始化一下。

class Stack
{
public:
	Stack(int capacity = 4)
		: _top(0)
		, _capacity(capacity)
		, _a((int*)malloc(sizeof(int) * capacity))
	{
		if (_a == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
	}
	
private:
	int* _a;
	int _top;
	int _capacity;
};

Stack的初始化列表除了可以像上面那样写,初始化列表还可以和函数体内赋初值一起使用,见下方代码:

#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 4)
		: _top(0)
		, _capacity(capacity)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

因为初始化列表有些事情做不了,所以可以在构造函数内做。比如:给申请的空间全部赋上初值 0 memset(_a, 0, sizeof(int) * capacity)

注意:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。

以上的例子似乎都无法解释初始化列表为什么要存在,因为似乎初始化列表做的时期,在函数里面也能做。

那什么样的场景一定需要初始化列表,而函数体内赋初值无法完成呢?我们一起来看一下:

在这里插入图片描述
当类B的成员变量用const修饰时,如果在函数体内给const修饰的成员变量赋初值,这时候就会出现语法错误。这就相当于修改一个const修饰的只读变量。const关键字修饰的变量在定义的时候必须初始化,而且只能初始化一次。所以这时候就需要借助初始化列表来完成这个工作了。因为对象的实例化是调用构造函数来整体定义的,而对象中的成员变量是在初始化列表中定义初始化的。

#include <iostream>
using namespace std;
class B
{
public:
	B()
		:_n(10)
	{}
private:
	const int _n; // const
};

int main()
{
	B b;

	return 0;
}

注意:对象的每个成员变量都需要走初始化列表,就算显式在初始化列表写,也会走。 那么如果不在初始化列表中初始化类B_n,就会报错。因为初始化列表是每个成员变量定义的地方,而const修饰的变量在定义的时候必须初始化且只能初始化一次。

在这里插入图片描述
如果你没有显式写初始化列表,也可以用缺省值的方式来解决。因为没有显式写的时候,初始化列表就会用上这个缺省值;如果写了,初始化列表就不会用这个缺省值。

class B
{
public:
	B()
	{}
private:
	const int _n = 1; // const
};

int main()
{
	B b;
	return 0;
}

注:如果没有在初始化列表显式初始化,对于自定义类型,有缺省值就用缺省值,没有缺省值就用随机值;而对于自定义类型,就会去调用该自定义类型的默认构造函数。如果没有默认构造函数,就会报错。

在这里插入图片描述

可以看到,上面的例子就很好地验证了上面的结论。那我们只需要提高类A的默认构造函数就可以解决上面的问题了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果显示写了初始化列表,就会用初始化列表的初始化。

class A
{
public:
	A(int a = 1)
		:_a(a)
	{}
private:
	int _a;
};

class B
{
public:
	B()
		: _n(10)
		, _aa(100)
	{}
private:
	const int _n = 1; // const
	A  _aa;
};

那么,我们来看一下队列MyQueue的初始化列表。

#include <iostream>
using namespace std;

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	// st2(st1)
	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int) * st._top);

		_top = st._top;
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// 扩容
		_a[_top++] = x;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue {
public:
	MyQueue()
	{}

	void Push(int x)
	{
		_pushST.Push(x);
	}
private:
	Stack _pushST;
	Stack _popST;
	int _size;
};

int main()
{
	MyQueue q;

	return 0;
}

在这里插入图片描述

可以看到,队列MyQueue的初始化列表什么都不写也可以,因为对于自定义类型,会调用该自定义类型的默认构造函数。如果没有提供栈Stack的默认构造函数或者对队列MyQueue的初始空间有要求,就需要写初始化列表了。

#include <iostream>
using namespace std;

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	// st2(st1)
	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int) * st._top);

		_top = st._top;
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// 扩容
		_a[_top++] = x;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue {
public:
	MyQueue(int capacity)
		: _pushST(capacity)
		, _popST(capacity)
	{}

	void Push(int x)
	{
		_pushST.Push(x);
	}
private:
	Stack _pushST;
	Stack _popST;
	int _size;
};

int main()
{
	MyQueue q(100);

	return 0;
}

这就是初始化列表的基本内容了。那我们再来想一个问题:什么样的成员变量一定需要初始化列表来初始化?是不是引用一定要在初始化列表中初始化,因为引用只有一次初始化的机会且也只能初始化一次。

结论:
类中包含以下成员,必须放在初始化列表位置进行初始化

  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

注意:尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。

接下来我们来看一道题目:

#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main() 
{
	A aa(1);
	aa.Print();
} 
//A.输出1 1
//B.程序崩溃
//C.编译不通过
//D.输出1 随机值

可能很多人的答案都是 A,但是正确答案是 D。
在这里插入图片描述
那为什么是这样呢?是因为成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

从这道题里可以看出,选择题是非常坑的。做选择题一定要小心,不小心一点就会掉进出题人埋的坑了。注:说得比较绝对的选项大概率是错的。 比如:一个类必须提供默认构造函数,不提供就会报错。这句话就是错的,只要我们不调用就不会报错,调用了就会报错。

#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main() 
{

	return 0;
}

在这里插入图片描述

explicit 关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有隐式类型转换的作用。

所谓的隐式类型转换,就是用一个类型的数据给不同类型的数据进行赋值时产生的转换。进行隐式转换时,会产生临时变量,临时变量具有常属性。因为临时变量具有常属性,所以使用引用时需要加上const修饰。

在这里插入图片描述

那自定义类型呢,也支持隐式类型转换,但要求其构造函数只有单个参数或者除第一个参数无默认值其余均有默认值。

单参数构造函数隐式类型转换(C++98)

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022);
	//隐式类型转换
	Date d2 = 2022;
	const Date& d3 = 2022;

	return 0;
}

在这里插入图片描述
explicit 修饰构造函数禁止隐式类型转换

如果我不想让自定义类型的隐式转换发生,我们就可以在构造函数前加上explicit关键字。

在这里插入图片描述
那这种隐式类型转换有什么用呢?有了自定义类型的隐式类型转换就会非常地方便,见下方代码:

#include <iostream>
using namespace std;
#include <string>

void push_back(const string& str)
{
	//...
}

int main()
{
	string s1("hello");
	push_back(s1);

	string s2 = "hello";
	push_back(s2);

	push_back("hello");

	return 0;
}

在编译器的优化过后,它们都只需要调用构造函数就行了,且用起来很方便。所以支持自定义类型的隐式类型转换还是非常必要的。

多参数构造函数隐式类型转换(C++11)

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1 = { 2022, 11, 13 };

	// 等价于
	Date d2(2022, 11, 13);

	const Date& d3 = { 2022, 11, 13 };

	return 0;
}

👉static 成员👈

概念

声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称之为静态成员变量;用 static 修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。

特性

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加 static 关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问
  4. 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  6. static 成员变量在对象生成之前生成

那 static 成员有什么用呢?我们先来看一道面试题:实现一个类,计算程序中创建出了多少个类对象。

#include <iostream>
using namespace std;

int N = 0;
class A
{
public: 
	A(int a = 10)
		:_a(a)
	{
		++N;
	}

	A(const A& aa)
		:_a(aa._a)
	{
		++N;
	}
private:
	int _a;
};

void Func1(A aa)
{}

A Func2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1(1);
	A aa2 = 2;

	A aa3 = aa1;

	cout << N << endl;

	Func1(aa1);
	cout << N << endl;

	Func2();
	cout << N << endl;

	return 0;
}

在这里插入图片描述
通过全局变量计数的方式,就可以知道一个程序创建了多少个类对象,但是这种方式不是很好。因为全局变量在哪个地方都可以改,没有封装起来,而 C++ 是很注重数据的封装的。那我们可以通过 static 成员变量来解决这个问题。

#include <iostream>
using namespace std;

class A
{
public: 
	A(int a = 10)
		:_a(a)
	{
		++N;
	}

	A(const A& aa)
		:_a(aa._a)
	{
		++N;
	}
private:
	int _a;
public:
	static int N; // static成员变量,并且类的每个对象共享
};

// 生命周期是全局的,作用域受类域限制的
int A::N = 0; // 定义初始化

void Func1(A aa)
{}

A Func2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1(1);
	A aa2 = 2;

	A aa3 = aa1;

	cout << A::N << endl;

	Func1(aa1);
	cout << A::N << endl;

	Func2();
	cout << A::N << endl;
	cout << aa1.N << endl;

	A* ptr = nullptr;
	cout << ptr->N << endl;

	return 0;
}

在这里插入图片描述
注:static 成员变量不在类对象里,static 成员变量在静态区中,其生命周期是全局的,作用域受类域的限制。

虽然解决了全局变量的问题,又带来了另一个问题:static 成员变量是公有的,没有将其封装起来。那有没有更好的方式呢?其实还有哦。

可以借助一个辅助的函数 GetN 来帮我们拿到 N 的值。

#include <iostream>
using namespace std;

class A
{
public:
	A(int a = 10)
		:_a(a)
	{
		++N;
	}

	A(const A& aa)
		:_a(aa._a)
	{
		++N;
	}

	int GetN()
	{
		return N;
	}

private:
	int _a;
	static int N; // static成员变量,并且类的每个对象共享
};

// 生命周期是全局的,作用域受类域限制的
int A::N = 0; // 定义初始化

void Func1(A aa)
{}

A Func2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1(1);
	A aa2 = 2;

	A aa3 = aa1;

	cout << aa1.GetN() << endl;

	Func1(aa1);
	cout << aa1.GetN() << endl;

	Func2();
	cout << aa1.GetN() << endl;

	return 0;
}

如果用 static 来修饰辅助函数 GetN 的话,那么得到 N 的值还有通过这种方式A::GetN()不过需要注意的是,static修饰的成员函数没有 this 指针,只能访问静态成员变量。

注意:静态成员函数只可以调用静态成员函数,不能调用非静态成员函数,而非静态成员函数可以调用静态成员函数。

接下来,我们来做一下这道题目:

在这里插入图片描述
那么这道题就可以借助 static 成员变量和 static 成员函数来解决了。

class Sum
{
public:
    Sum()
    {
        _ret += _i;
        ++_i;
    }

    static int GetRet()
    {
        return _ret;
    }

private:
    static int _ret;
    static int _i;
};

int Sum::_ret = 0;
int Sum::_i = 1;

class Solution 
{
public:
    int Sum_Solution(int n) 
    {
        Sum arr[n]; // 调用n次构造函数
        return Sum::GetRet();
    }
};

要求类对象只能在栈区上创建

类对象的创建都是通过调用构造函数来创建的,那么我们将构造函数改成私有属性,再写一个辅助函数来帮我们创建对象。这时候,创建出来的类对象都只能是在栈区上创建的。

#include <iostream>
using namespace std;

class A
{
public:
	static A GetObj(int a = 0)
	{
		A aa(a);
		return aa;
	}

private:
	A(int a = 10)
		:_a(a)
	{}

private:
	int _a;
};

// 要求类只能在栈上创建
int main()
{
	//static A aa1;
	//A* ptr = new A;
	//A aa2;

	A aa3 = A::GetObj(10);
}

👉友元👈

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元分为友元函数和友元类。

友元函数

问题:现在尝试去重载 operator<<,然后发现没办法将 operator<< 重载成成员函数。因为 cout 的输出流对象和隐含的 this 指针在抢占第一个参数的位置。this 指针默认是第一个参数也就是左操作数了。但是实际使用中 cout 需要是第一个形参对象,才能正常使用。所以要将operator<< 重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>> 同理。

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字。注:声明可以在类的任何位置声明。

#include <iostream>
using namespace std;

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

说明:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用 const 修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  • 友元关系是单向的,不具有交换性。
  • 友元关系不能传递。
    如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承,在继承位置再给大家详细介绍。
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;
};

👉内部类👈

概念

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性

  1. 内部类可以定义在外部类的 public、protected、private 都是可以的。
  2. 注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象或类名。
  3. sizeof(外部类) = 外部类,和内部类没有任何关系。

sizeof(外部类) = 外部类的大小

#include <iostream>
using namespace std;

class A
{
private: 
	int _a;
public:
	class B // B天生就是A的友元
	{
		int _b;
	};
};

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

在这里插入图片描述
外部类A和内部类B是相当于两个独立的类,只是内部类B的访问受外部类A的类域和访问限定符的限制。

#include <iostream>
using namespace std;

class A
{
private:
	int _a;
	static int k;
public:
	class B // B天生就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl; // OK
			cout << a._a << endl; // OK
		}
	private :
		int _b;
	};
};
int A::k = 1;

int main()
{
	A::B b;
	b.foo(A());

	return 0;
}

注:外部类不能访问内部类的成员。

现在我们通过内部类来做一下上面那道求1+2+...+n的和的题。

class Solution 
{
public:
    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            ++_i;
        }

        static int GetRet()
        {
            return _ret;
        }
    };

    int Sum_Solution(int n) 
    {
        Sum arr[n]; // 调用n次构造函数
        return _ret;
    }

private:
    static int _ret;
    static int _i;
};

int Solution::_ret = 0;
int Solution::_i = 1;

👉匿名对象👈

#include <iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	// 有名对象
	A aa1(1);
	A aa2 = 2;

	// 匿名对象 -- 其生命周期就是当前这一行
	A();
	A(3);

	return 0;
}

在这里插入图片描述
有了匿名对象,我们可以这样使用:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

A Func()
{
	//A ret(10);
	//return ret;
	return A(10);
}

👉拷贝对象时的一些编译器优化👈

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

在这里插入图片描述
本来应该是先调用构造函数产生一个临时的对象A tmp,再调用拷贝构造函数创建A aa1,但是这个过程被编译器优化成直接用A aa1(1),只调用一次构造函数。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
编译器做这些优化也是很有必要的,比如进行数据结构的深拷贝之类的操作。所以我们在写程序的时候也要考虑一下会不会触发编译器的优化机制。

👉再次理解类和对象👈

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:

  1. 用户先要对现实中洗衣机实体进行抽象—即在人为思想层面对洗衣机进行认识,洗衣机有什 么属性,有那些功能,即对洗衣机进行抽象认知的一个过程。
  2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清 楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中。
  3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣 机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才 能洗衣机是什么东西。
  4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。

在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。

在这里插入图片描述

👉总结👈

本篇博客主要讲解了初始化列表、explicit 关键字、static 成员、友元、内部类、匿名对象和编译器的优化,希望大家能够掌握。以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!💖💝❣️

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

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

相关文章

kindle自定义屏保之自定义字帖

kindle自定义屏保之自定义字帖 01 前言 毕业以后&#xff0c;很少动笔写字了&#xff0c;某天要手写一堆材料&#xff0c;写出来实在不忍直视&#xff0c;于是当晚下班后突发奇想——能不能把一些字帖搞成kindle屏保&#xff0c;摆在桌面上&#xff0c;睡前说不准还能练练 随…

web课程设计 基于html+css+javascript+jquery女性化妆品商城

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

软件测试基础知识

软件测试基础知识1.测试模型2.测试分类3.测试目的与原则4.测试流程5.测试发展规划6.单元测试7.黑盒测试8.白盒测试9.缺陷1.测试模型 瀑布模型 开发将系统都做好了&#xff0c;然后测试。最大问题是测试工作后置&#xff0c;导致整个项目开发完成之后如果发现比较重要的问题&…

基于微信小程序的校运会管理系统设计与实现-计算机毕业设计源码+LW文档

小程序开发说明 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Mave…

【大学课程设计】计算器实现(附源码)

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

6.Paddle Graph Learning (PGL)图学习之图游走类模型[系列四]

Paddle Graph Learning (PGL)图学习之图游走类模型[系列四] 更多详情参考&#xff1a;Paddle Graph Learning 图学习之图游走类模型[系列四] https://aistudio.baidu.com/aistudio/projectdetail/5002782?contributionType1 相关项目参考&#xff1a; 关于图计算&图学习…

路由规划——运输距离的估算

运输距离的估算1. 常规的拟合距离1.1 欧氏距离1. 2 球面距离拟合2. 一种改进的球面距离拟合参考文献在进行路径规划时&#xff0c;需要获取点与点之间的距离&#xff0c;点之间的距离通常是通过坐标或者经纬度计算得到&#xff0c;可分为拟合距离和导航距离两类。导航距离顾名思…

基础二叉树及其高频面试题

目录 一、树的概念及其结构 1.1 树的概念 1.2 树的相关概念 1.3 树的表示法 二、二叉树的概念及其结构 2.1 概念 2.2 特殊二叉树 2.3 二叉树的性质 2.4 二叉树的存储结构 顺序存储 链式存储 三、链式二叉树 3.1 遍历方式 深度优先遍历:DFS 层序遍历:BFS(广度优先…

【附源码】Python计算机毕业设计社区防疫信息管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Debian11中 Nginx1.22.1 php8.1.12 Mariadb10.5.15的安装

起因&#xff1a;我之前发的关于LEMP的搭建的文章&#xff0c;已经有3年9个月了&#xff0c;各个软件的版本更新了比较多。最主要的是&#xff0c;CentOS系统终止了&#xff0c;我也完全更换到了Debian系统之上。这里重新搭建了一下&#xff0c;主要是计划结合frp&#xff0c;构…

Vue | Vue.js 全家桶 Pinia状态管理

&#x1f5a5;️ Vue .js专栏&#xff1a;Node.js Vue.js 全家桶 Pinia状态管理 &#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; ✨ 个人主页&#xff1a;CoderHing的个人主页 &#x1f340; 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀…

[基础服务] [操作系统] 类Linux的文件和目录

&#x1f341;简介 在奔腾70年代的中美建交之际,UNIX 也逐渐展露头角(也有说是60年代末),十五年后Windows诞生了,又过了五年Linux横空出世三大主流操作系统直到现在呈现三足鼎立之势~ 出生时间是&#xff1a; UNIX(70年代初) > Windows(80年代中) > Linux (90年代初) 可以…

基于微信小程序的电影院票务系统设计与实现-计算机毕业设计源码+LW文档

小程序开发说明 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Mav…

HCIP-Datacom OSPF进阶(二)最常用的路由协议 OSPF各种LSA作用详解

目录 OSPF路由计算&#xff1a; LSA头部信息&#xff1a; Router-LSA&#xff08;1类&#xff09;&#xff1a; 一类LSA&#xff1a; 一类LSA可以描述四种链路类型&#xff1a; Network-LSA&#xff08;2类&#xff09;&#xff1a; 二类&#xff1a; IR、ABR、ASBR是什…

【HTML实战】把专属于她的爱心代码放在自己的网站上是一种什么体验?

一、 写在前面的话 看多了李洵的炫酷爱心&#xff0c;今天来点不一样的爱心代码。需要李洵的炫酷爱心代码请看我的上一篇文章。 最近随着电视剧《点燃我温暖你》的火热播出&#xff0c;剧中帅气学霸李洵的炫酷爱心代码也迅速火出了圈&#xff0c;作为一个喜欢动手实践的我来说…

力扣206 - 反转链表【校招面试高频考题】

乾坤大挪移~一、题目描述二、思路分析1、头插2、三指针迭代三、整体代码展示【需要自取】1、头插2、三指针迭代四、总结与提炼一、题目描述 原题传送门 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xf…

【Redis】CentOs 虚拟机安装 Redis 缓存数据库

一、使用案例&#xff1a; 以淘宝为例&#xff0c;几个人一起登入淘宝&#xff0c;去检索男装&#xff0c;这几个人的访问都得到相同的结果。这时&#xff0c;我们就可以考虑将男装缓存在一个地方&#xff0c;而不是再去访问数据库&#xff0c;这时&#xff0c;就是我们 Redis…

ctfshow文件包含

web78 源码&#xff1a; if(isset($_GET[file])){$file $_GET[file];include($file); }else{highlight_file(__FILE__);//高亮显示当前文件内容 }解法一 - php://input # http://challenge.ctf.show/?filephp://input # [POST DATA] <?php system(ls)?> <?php…

基于可视图法(VG)的路径规划算法简述

可视图法路径规划&#xff08;VG&#xff09; 可视图法由Lozano-Perez和Wesley于1979年在论文&#xff1a;《An Algorithm for Planning Collision-Free Paths among Polyhedral Obstacles.》中提出。 基于可视图法路径规划算法主要包括以下两个步骤&#xff1a;①可视图的构建…

第二章 环境的选择和安装

1、开发环境操作系统 1.1 选择Windows、Mac or Linux 首先明确一点&#xff1a;我们的首要目的是要快速上手使用Elasticsearch&#xff08;以下简称ES&#xff09;&#xff0c;安装和部署ES并非重点&#xff0c;企业中真正需要你去安装的可能性或者机会非常小。大家可以想一想…