【C++初阶】第四站:类和对象(下)(理解+详解)

前言:

本篇知识点:初始化列表、explicit关键字、static成员、友元、内部类、匿名对象、编译器的优化

专栏:C++初阶

目录

再谈构造函数

1️⃣构造函数体赋值

2️⃣初始化列表

explicit关键字

static成员

1.static概念

2.static特性

面试题

友元 

友元函数

友元类

内部类

内部类概念

优化面试题

匿名对象

 匿名对象和有名对象

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

知识点回顾:

示例(包含讲解): 

传值传参和传值返回

构造+拷贝构造

连续的拷贝构造

拷贝构造+赋值重载(无法优化) 

再次理解类和对象


再谈构造函数

        对于MyQueue 不需要写它的构造函数,编译器自动生成,会调用它的默认构造。

但是如果Stack类不提供默认构造给你,那就得实现显示调用,该怎么办呢?

有两种办法。

1️⃣构造函数体赋值

在创建对象时,编译器通过调用 构造函数,给对象中各个成员变量一个合适的初始值。
代码示例:
class Date
{
public:
    Date(int year, int month, int day)
    {
         _year = year;
         _month = month;
         _day = day;
     }
private:
    int _year;
    int _month;
    int _day;
};
        虽然上述构造函数调用之后,对象中已经有了一个初始值,但是 不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为 赋初值,而不能称作 初始化因为初始化只能初始化一次,而构造函数体内可以多次 赋值
如下例子:
​class Date
{
public:
    Date(int year=2024, int month=1, int day=1)//构造函数初始化,只能初始化一次
     //赋值
    {
         _year = year;//可以多次赋值
        _year = 2023; 
        _year = 2021; 
        //...
        _month = month;
         _day = day;
     }
private:
    int _year;
    int _month;
    int _day;
};

2️⃣初始化列表

初始化列表:以一个 冒号 开始,接着是一个以 逗号 分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式
代码如下:
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{
        }
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{	
	Date d1(2024, 1, 1);
	d1.Print();
	return 0;
}

执行如下:

【注意】
1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 )
2. 类中包含以下成员, 必须放在初始化列表 位置进行初始化:
💦引用成员变量
💦const成员变量
💦自定义类型成员(且该类没有默认构造函数时)
其实可以这样理解:
代码如下:
class Date
{
public:
	//初始化列表是每个成员定义的地方
	//不管你写不写,每个成员都要走初始化列表
	Date(int year, int month, int day, int& i)
		:_year(year)
		, _month(month)
		, _a(i)
		, _refi(i)
		,_x(100)//显示给值了
	{
		//赋值
		//_day = day;
	}
	void func()
	{
		++_refi;
		++_refi;
	}

//private下面如果成员变量右边给了值,都叫做缺省值
private:
	int _year;//每个成员声明
	int _month;
	int _day;
	//C++11支持给缺省值,这个缺省值给初始化列表
	//如果初始化列表没有显示给值,就用这个缺省值

	//必须定义时初始化,也就是说以下这三个成员变量必须出现在初始化列表
	const int _x=1;//如果显示给值了,就不用这个缺省值
	int& _refi;
	A _a;
};
//能用初始化列表就用初始化列表初始化
//有些场景还是需要初始化列表和函数体混着用
int main()
{
	int n = 0;
	Date d1(2023, 7, 28, n);
	d1.func();

	cout << n << endl;
	return 0;
}

执行:对_refi就是对着n操作

3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表, 对于自定义类型成员变量, 一定会先使用初始化列表初始化。
#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private: 
	int _hour;
};
class Date
{
public:
	Date(int day)
	{}
private:
	int _day;
	Time _t;
};
int main()
{
	Date d(1);
}

执行:

4. 成员变量在类中 声明次序 就是其在初始化列表中的 初始化顺序 与其在初始化列表中的先后次序无关
来做一道题:

explicit关键字

        构造函数不仅可以构造与初始化对象,对于单个参数的 构造函数,还具有类型转换的作用
class A
{
public:
	//explicit A(int i)
	 A(int i)
		:_a(i)
	{
		cout << "A(int i):"<<i<< endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const & aa)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
struct SeqList
{
public:
	void PushBack(const A& x)
	{
			//...扩容
		_a[_size++] = x;
	}
	size_t size() const
	{
		return _size;
	}
	//读
	const A& operator[](size_t i)const
	{
		assert(i < _size);
		return _a[i];
	}
	//读/写
	A& operator[](size_t i)
	{
		assert(i < _size);
		return _a[i];
	}
private:
	//C++11
	A* _a = (A*)malloc(sizeof(A) * 10);
	size_t _size = 0;
	size_t _capacity = 0;
};
int main()
{
	A aa1(1);
	A aa2 = 2;
	return 0;
}

       经过编译器优化之后,以下的两个代码是等价的:

A aa1(2);//直接构造 <==> A aa1 = 2

解析:        

        在早期的编译器中,当遇到下面的一行代码时,会处理成:用2调用A构造函数生成一个临时对象(tmp),再用这个对象(tmp)去拷贝构造aa1

A aa1 = 2;//先构造,再拷贝构造

上面的代码等价于下面这两步:

A tmp(2);
A aa2(tmp);  

但是,C++支持单参数构造函数的隐式类型转换

        编译器会再优化,优化用2直接构造,所以当我们遇到像A aa1 = 2的式子时,实际上编译器已经转换成了A aa1(2),这就叫隐式类型转换

同时在c++中,不想让隐式类型发生,就在构造函数前面加个explicit

因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用

在C语言中我们也讲了隐式类型转换:无论是值拷贝还是说加了引用的,都会生成临时变量的。

static成员

1.static概念

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

2.static特性

  1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区
代码如下:
#include<iostream>
using namespace std;
class A
{
public:
	A() 
	{
		++n;
		++m;
	}
	A(const A& t)
	{
		++n;
		++m;
	}
	~A()
	{
		--m;
	}
private:
	int a;//4 byte
	static int n;
	static int m;
};
int main()
{
	cout << sizeof(A) << endl;
	return 0;
}

2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

解析思路:
当我们要计算A这个类,累计创建了多少个对象(用n表示),正在使用的多少个对象(用m表示)
以之前的知识,我们首先会在全局定义两个变量

 经过以下代码验证之后,我们发现,如果定义全局的变量,会被外面随意修改

       

        此时的话,我们试下把n和m定义在class A的private内,但是这样每一个对象在定义的时候,都会创建一个n和m,此时n和m是每一个对象的成员了,不是用来统计有几个对象,明显不能这样定义。

这时候如果被static修饰,这两个成员变量就位于静态区了,叫做静态成员变量,需要注意的地方有:

 代码如下:

class A
{
public:
private:
	int a;//4 byte
	static int n;//静态成员的声明
	static int m;//静态成员的声明
};
//在类外面定义
int A::n = 0;
int A::m = 0;

还有要注意的;

3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问 

静态成员变量被public修饰时:

 代码如下:

include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		++n;
		++m;
	}
	A(const A& t)
	{
		++n;
		++m;
	}
	~A()
	{
		--m;
	}
//private:
	static int n;
	static int m;
};
// 静态成员变量的定义初始化
int A::n = 0;
int A::m = 0;

int main()
{
	A aa1;
	cout << A::n << " " << A::m << endl;//1.通过类名突破类域进行访问
	cout << aa1.n << " " << aa1.m << endl; //2.通过类对象突破类域进行访问
	A* ptr = NULL;
	cout << ptr->n << " " << ptr->m;//3.通过空指针解引用成员突破类域
}

当静态成员变量被private修饰时:

        我们当然可以定义被public修饰的成员函数,然后此时被static修饰的两个静态成员n和m通过创建对象aa1,接着aa1.Print()就可以打印出对象的个数,

        但如果我定义一个匿名对象,接着调用Print函数,这时候会多出一个n(累计创建的对象),干扰打印的逻辑了。

我们可以借鉴上面静态成员变量突破类域的方式,引出我们静态成员函数的三种突破类域的方式

代码如下: 

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A()" << endl;
		++n;
		++m;
	}
	A(const A& t)
	{
		++n;
		++m;
	}
	~A()
	{
		--m;
	}
	//静态成员函数的特点:没有this指针
	static int GetM()
	{
		return m;
	}
	static void Print()
	{
		//x++;//不能访问非静态,因为没有this
		cout << m << " " << n << endl;
	}
private:
    //这样定义不行,这样的话每一个对象都有一个n和m,
    //int m;
	//int n;
	// 不符合题意,因为这是来统计对象个数的


	//静态成员变量属于所有A对象,属于整个类
	//声明
	//累积创建了多少个对象
	static int n;
	//正在使用的还有多少个对象
	static int m;
};

int A::n = 0;
int A::m = 0;

int main()
{
	A aa;
    
//三种突破类域的方式
	A::Print();//通过类名调用静态成员函数进行访问
	aa.Print();//通过实例化的对象调用成员函数进行访问
	A* ptr = NULL;
	ptr->Print();//通过空指针调用静态成员函数进行访问

	return 0;
}
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

代码如下:

//4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
#include<iostream>
using namespace std;
class A
{
public:
	static void Print()
	{
		cout << x++ << endl;
	}
private:
	 int x;//非静态成员变量
	 static int a;//静态成员变量
};
int main()
{
	A x;
	x.Print();
}
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
【问题】
1. 静态成员函数可以调用非静态成员函数吗?
答:不能 , 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
2. 非静态成员函数可以调用类的静态成员函数吗?
答:可以。 因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制。
代码如下:
#include<iostream>
using namespace std;
class A
{
public:
	void Notstatic()
	{
		Print();//<-------非静态调用调用静态 
	}
    //两种写法
	static void Print()
	{
		cout << a << endl;
	}
	/*static int Print()
	{
		cout << a << endl;
		return a;
	}*/
private:
	 int x;//非静态成员变量
	 static int a;//静态成员变量
};
int A::a = 10;
int main()
{
	A x;
	x.Notstatic();
}

执行:

面试题

1. 1+2+3+...+n ,要求不能使用乘除法、 for while if else switch case 等关键字及条件判
断语句: 求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
#include<iostream>
using namespace std;
class Sum//定义一个名为Sum的类
{
public:
    Sum()//构造函数,当创建Sum对象时自动调用
    {
        _ret +=_i;// 每次构造函数被调用时,将静态成员变量_i的当前值累加到静态成员变量_ret上
        _i++; //紧接着递增静态成员变量_i的值
    }
    static int GetRet()//定义一个静态成员函数GetRet,用于获取静态成员变量_ret的值
    {
        return _ret;//直接返回静态变量_ret的值
    }
private:
//定义两个静态私有成员变量
// 静态成员变量属于类,不是某个对象所有,而是所有对象共享,并且在整个程序生命周期内只初始化一次
    static int _i;//初始化为1,每次构造函数调用时递增
    static int _ret;// 初始化为0,用于累计构造函数调用次数
};

//对静态成员变量进行初始化(定义)
int Sum::_i = 1;
int Sum::_ret = 0;
//定义另一个名为Solution的类
class Solution {
public:
    // 定义成员函数Sum_Solution,接收一个整数参数n
    int Sum_Solution(int n) {
        Sum a[n];
        return Sum::GetRet();
    }
};

友元 

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

友元函数

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

我们之前在:【C++初阶】第一站:C++入门基础(上) -- 良心详解-CSDN博客

简单了解过关于cout(流插入),cin(流提取)的知识

现在我们来回顾一下:

在之前的印象中,当我们遇到关于内置类型(int,float,double等),可以直接使用cout和cin进行输出和输入:

原因是什么呢,通过查阅资料可以发现:i 和 j 通过操作符重载间接实现了类似成员函数的功能。

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

在C++中,内置类型是直接支持cout流插入<<和cin流提取>>,这并不是什么自动识别类型,是运算符重载和函数重载罢了,库里面支持把内置类型作为成员函数,重载了

这时候,我们创建Date类的一个自定义类型的对象,使用cout和cin输出和输入会发现编译错误:

我们可以看到,隐含的this指针,占据着这个流插入成员函数的第一个参数的位置,与main函数内调用的位置不相符,cout是ostream类型的对象,但是到了成员函数,第一个位置是Date*类型

 既然它的位置不相符,那么我们可以这样写吗:

        可以是可以,但是流插入的本质是:应该是对象流入到console里面去,而不是console流入到对象里 ,对于流提取同理

 这时候我们把位于Date.h里原本成员函数的声明注释掉:

 我们在全局定义一个<<重载的函数,定义成全局的声明,此时经过编译后,又引发了一个新问题:

面对这样的情况,该如何去纠正: 

在类的外部要想访问内部私有成员,用友元声明:在类的公有和私有声明都可以

我们发现就可以编译通过了:

 并且类型的顺序也是匹配的:

但是当咱们连续输出两个自定义对象的时候,编译就不会通过了,看下面解析:

这时候我们把.h里面的友元的返回值改成ostream&、全局声明和.cpp里面的返回值也改成一样:

 对于流提取,并不能给声明加const:

总结:

内置类型,可以使用<<>>是因为函数重载加运算符重载

自定义类型使用的方式是重载这个流插入和流提取的运算符

问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。
因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。
class Date
{
public:
    Date(int year, int month, int day)
     : _year(year)
     , _month(month)
     , _day(day)
     {}
// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
    ostream& operator<<(ostream& _cout)
    {
     _cout << _year << "-" << _month << "-" << _day << endl;
     return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};
所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加friend关键字。

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
  • 友元关系是单向的,不具有交换性。
        比如下面的Time类和Date类, 在Time类中声明Date类为其友元类 那么可以在Date类中直接
访问Time类的私有成员变量, 但想在Time类中访问Date类中私有的成员变量则不行
  • 友元关系不能传递
        如果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.B类 受A类域和访问限定符的限制,其实它们是两个独立的类
2.内部类默认就是外部类的友元 -- 内部类可以访问外部类,外部类不能访问类部类
特性:
1. 内部类可以定义在外部类的 public protected private 都是可以的。
2. 注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。
3. sizeof( 外部类 )= 外部类,和内部类没有任何关系。
代码示例:
class A
{
private:
     static int k;
     int h;
public:
     class B // B天生就是A的友元
     {
 public:
     void foo(const A& a)
     {
         cout << k << endl;//OK
         cout << a.h << endl;//OK
     }
     };
};
int A::k = 1;
int main()
{
    A::B b;
    b.foo(A());
    
    return 0;
}

执行:

优化面试题

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

class Solution {
    class Sum {
    public:
        Sum()
        {
            _ret += _i;
            _i++;
        }
         };
  public:
    int Sum_Solution(int n) {
        Sum a[n];
        return _ret;
    }
    private:
        static int _i;
        static int _ret;
};
int Solution::_i = 1;
int Solution::_ret = 0;

匿名对象

#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;
};
class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	A aa1;
	// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa1();

	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();
	A aa2(2);
	// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
	Solution().Sum_Solution(10);
	return 0;
}

执行:

 匿名对象和有名对象

例子

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

知识点回顾:

        C++默认兼容C语言,默认生成的拷贝构造函数对内置类型会完成值拷贝(跟结构体拷贝一样)但是又规定自定义类型传值传参过程中,符合拷贝构造:拿一个以及存在的对象去初始化另一个对象

        为什么要规定调拷贝构造?因为直接搞值拷贝,会有很大的问题,比如说像栈、顺序表、链表这样的类(析构两次),对象里面可能还有一个指针指向一块空间,这时候就要完成深拷贝,那想让这个拷贝正确该怎么办呢,就要自己去写那个深拷贝

        所以c++在这一块完成了完美的兼容,对于日期类就算要浅拷贝(不写,编译器默认生成的拷贝构造),编译器有着个性化处理,对于栈要自己写深拷贝,对于日期类写不写都行

示例(包含讲解): 

比如下面这个例子中:传值传参引发了对象的拷贝,拷贝要调用拷贝构造,拷贝出aa1的副本aa,然后出了作用域aa先析构,回到main函数之后,出了作用域aa1再调析构

可以想象一下,假设我仅仅只想调用一下Print()有没有必要使用拷贝构造?没有吧。

我们要给这个形参加上引用,同时加上const,这样的话就不会引发拷贝,也保护了对象不可修改:

同时插播一下,这两者是有着显著区别的: 

其实对于void f1的(A& aa)这个地方可以不加const,这属于权限的缩小,但是对于匿名对象来说可不行
因为:f1(A())这一行试图将一个匿名临时对象传递给需要非const引用参数的函数f1(A& aa)
匿名临时对象不能绑定到非const引用上,因为匿名临时对象生命周期结束后会自动销毁,
而非const引用可能会尝试修改临时对象,这是不允许的。
所以要给这个函数加上 const-->void f1(const A& aa)
另外, 我们知道匿名对象的生命周期只在这一行,但是const引用会延迟匿名对象的生命周期:

传值传参和传值返回

对于编译器处理 传值传参和传值返回的总结:
传值返回 -- 不能带引用返回,因为aa出了这个作用域调析构了。 如果返回了aa的引用,意味着返回的引用指向了一个已经销毁的对象,在实际运行时可能导致各种难以预料的问题:
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	// 传值返回
	f2();
	cout << endl;
    return 0;
}

对于析构的分析

对于f2(),仅此于f2(),我们分析一下析构:

  1. 第一次析构:在 f2 函数内部,局部变量 aa 在 return aa; 语句处会触发一次析构。这是因为 aa 是 f2 函数的局部对象,当函数执行完毕时,局部对象的生命周期结束,因此会调用析构函数。

  2. 第二次析构:f2 函数返回的是 A 类的一个对象,但由于它是通过值返回的,所以在 f2() 调用处会创建一个临时对象接收返回值。然而,由于这个临时对象在表达式结束之后没有被存储到任何地方,因此它也会在表达式(也就是f2() )结束时立即被销毁,从而触发第二次析构。

构造+拷贝构造

一个表达式,连续的步骤里面,连续的构造会被合并

f1(1):隐式类型,连续构造(构造函数)+拷贝构造->优化为直接构造

f1(A(2)):一个表达式中, 连续构造(构造函数)+拷贝构造->优化为一个构造

连续的拷贝构造

一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造

拷贝构造+赋值重载(无法优化) 

一个表达式中,连续拷贝构造+赋值重载->无法优化

//上文有A类的定义
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	A aa1;
	aa1 = f2();
	cout << endl;
	return 0;
}

 执行:

        拷贝构造的aa,返回的临时拷贝,也就是回到main函数之后的那个临时对象(黄色字),要等到赋值运算符重载完毕之后,才调的析构

再次理解类和对象

类和对象篇就此结束,接下来是内存管理。

🔧本文修改次数:0

🧭更新时间:2024年3月1日

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker技术概论(4):Docker CLI 基本用法解析

Docker技术概论&#xff08;4&#xff09; Docker CLI 基本用法解析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:http…

NC65 零预算控制规则 数据库表关系

NC65 零预算控制规则 数据库表关系 SELECT t1.createdby, t1.objname, t2.ctrlname, t2.pk_parent, t3.billtype, t3.nameidx, t3.pk_obj FROM tb_rule_formula t1 left join tb_ctrlformula t2 on t1.pk_obj t2.pk_parent left join tb_ctrlscheme t3 on t3.pk_ctrlformula …

Mysql安装教程

一、下载 点开下面的链接&#xff1a;https://dev.mysql.com/downloads/mysql/ 点击Download 就可以下载对应的安装包了, 安装包如下: 二、解压 下载完成后我们得到的是一个压缩包&#xff0c;将其解压&#xff0c;我们就可以得到MySQL 8.0.31 的软件本体了(就是一个文件夹…

Tomcat布署及优化-----JDK和Tomcat

1.Tomcat简介 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;Tomcat 属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试 JSP 程序的首选。一般来说&…

Vivado Vitis 2023.2 环境配置 Git TCL工程管理 MicroBlaze和HLS点灯测试

文章目录 本篇概要Vivado Vitis 环境搭建Vivado 免费标准版 vs 企业版Vivado Windows 安装Vivado 安装更新 Vivado 工程操作GUI 创建工程打开已有工程从已有工程创建, 重命名工程GUI导出TCL, TCL复原工程TCL命令 Vivado 版本控制BlinkTcl脚本新建导出重建工程纯Verilog BlinkTc…

抖音视频批量下载软件说明|视频采集挖掘工具

操作步骤如下&#xff1a; 打开抖音批量下载工具&#xff0c;进入软件界面的第一个选项卡页面。在搜索框中输入需要搜索的视频关键词&#xff0c;例如"汽车配件"&#xff0c;然后点击"开启抓取"按钮开始搜索。软件将开始搜索并显示与关键词相关的视频内容…

祖传代码:历史的宝藏与现代的挑战

程序员是如何看待“祖传代码”的&#xff1f; 程序员眼中的“祖传代码”&#xff0c;就像一本古老而神秘的魔法书&#xff0c;藏着无穷的智慧和技巧&#xff0c;有些代码像家传宝贝&#xff0c;有些像祖传秘方。快来分享一下你遇到的“祖传代码”吧~ 一、祖传代码的历史与文…

算法------(13)KMP

例题&#xff1a;&#xff08;1&#xff09;AcWing 831. KMP字符串 。。其实写完也不太理解。。随便写点吧 KMP就是求next数组和运用next的数组的过程。相比传统匹配模式一次更新一单位距离的慢速方法&#xff0c;next数组可以让下表字符串一次更新n - next【n】个距离&#x…

三天学会阿里分布式事务框架Seata-seata事务日志mysql持久化配置

锋哥原创的分布式事务框架Seata视频教程&#xff1a; 实战阿里分布式事务框架Seata视频教程&#xff08;无废话&#xff0c;通俗易懂版&#xff09;_哔哩哔哩_bilibili实战阿里分布式事务框架Seata视频教程&#xff08;无废话&#xff0c;通俗易懂版&#xff09;共计10条视频&…

虚拟机中Linux的安装与初始化配置(更新时间24/3/1)

先问三个问题 vm虚拟机安装了吗&#xff1f;点击此处跳转虚拟机安装教程Linux镜像下载了吗&#xff1f;点击此处跳转Linux镜像下载教程新建Linux虚拟机配置了吗&#xff1f;点击此处跳转新建虚拟机的配置教程 顺序是&#xff1a;下载虚拟机–>下载Linux镜像–>新建Linux配…

python63-Python的循环之循环使用else

Python的循环都可以定义else代码块&#xff0c;当循环条件为False 时&#xff0c;程序会执行else代码块。如下代码示范了为while循环定义else代码块。 # !/usr/bin/env python# -*- coding: utf-8 -*-# Time : 2024/01# Author : Laopicount_i 0while count_i < 5:print(c…

Verilog(未完待续)

Verilog教程 这个教程写的很好&#xff0c;可以多看看。本篇还没整理完。 一、Verilog简介 什么是FPGA&#xff1f;一种可通过编程来修改其逻辑功能的数字集成电路&#xff08;芯片&#xff09; 与单片机的区别&#xff1f;对单片机编程并不改变其地电路的内部结构&#xff0…

YOLOv9大幅度按比例减小模型计算量!加快训练!

一、代码及论文链接&#xff1a; 代码链接&#xff1a;GitHub - WongKinYiu/yolov9: Implementation of paper - YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information 论文链接&#xff1a;https://github.com/WongKinYiu/yolov9/tree/main 二…

Docker容器与虚拟化技术:OpenEuler 使用 docker-compose 部署 LNMP

目录 一、实验 1.环境 2.OpenEuler 部署 docker-compose 3.docker-compose 部署 LNMP 二、问题 1.ntpdate未找到命令 2.timedatectl 如何设置时区与时间同步 3.php网页显示时区不对 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统架构版本IP备注Lin…

Visual Studio:指针和固定大小缓冲区只能在不安全的上下文中使用、 设置允许使用不安全代码(unsafe)

问题描述: 指针和固定大小缓冲区只能在不安全的上下文中使用 解决方案&#xff1a; 1、解决方案资源管理器-》选择项目-》右键-》属性 2、在生成窗口中&#xff0c;勾选“允许不安全代码” 3、再次“生成解决方案”即可

C语言基础17 判断

断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 C 语言把任何非零和非空的值假定为 true&#xff0c;把零或 null 假定为 false。 下面是…

免费百度快速收录软件

在网站SEO的过程中&#xff0c;不断更新网站内容是提升排名和吸引流量的关键之一。而对于大多数网站管理员来说&#xff0c;频繁手动更新文章并进行SEO优化可能会是一项繁琐且耗时的任务。针对这一问题&#xff0c;百度自动更新文章SEO工具应运而生&#xff0c;它能够帮助网站管…

Maven面试题

以下是一些关于Maven的经典面试题以及它们的答案&#xff1a; 1、什么是Maven&#xff1f; Maven是一个项目管理工具&#xff0c;用于构建、管理、发布Java项目。 2、为什么要使用Maven而不是手动管理项目依赖&#xff1f; Maven提供了依赖管理、统一的构建、打包、文档生…

Unity(第二十四部)UI

在游戏开发中&#xff0c;用户界面&#xff08;UI&#xff09;是至关重要的一部分。它负责与玩家进行交互&#xff0c;提供信息&#xff0c;并增强游戏的整体体验。Unity 提供了强大的工具和功能来创建和管理 UI。 ui的底层就是画布&#xff0c;创建画布的时候会同时创建一个事…

rocketmq+rocket-dashboard win10安装部署+注册为Windows服务

1.1 首先去官网下载zip包 选择自己需要的版本 下载 | RocketMQ 1.2 、下载后&#xff0c;解压到指定目录 1.3、配置RocketMQ环境变量 注意&#xff0c;看对应的版本需要jdk版本 1.4、启动mqnameserver 进入bin目录下&#xff0c;双击启动mqnamesrv.cmd 启动后&#xff0c;…