C++类和对象详解(中篇)

C++类和对象详解(中篇),第1张

文章目录
  • 1:类的6个默认成员函数
  • 2:构造函数
    • 2.1:场景引用
    • 2.2:概念
    • 2.3:特点
    • 2.4:类别
    • 2.5:默认构造函数
      • 2.5.1:引出
      • 2.5.2:类型
  • 3:析构函数
      • 3.1:功能
      • 3.2:特点
      • 3.3:调用过程
  • 4:拷贝构造函数
    • 4.1:概念
    • 4.2:特征
    • 4.3:浅拷贝
  • 5:赋值运算符重载
    • 5.1:概念
    • 5.2:运算符重载
    • 5.3:赋值运算重载
      • 5.3.1:特征
      • 5.3.2:=赋值运算符重载举例

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

在C语言中,我们写栈,链表等等,需要自己手动初始化,手动销毁。

在c++中,一个空类中什么都不写的时候,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式定义的时候,编译器会默认生成的成员函数。

class date
{
    //...
};

2:构造函数 2.1:场景引用
#include
using namespace std;
class test
{
public:
	void init(int a, int b)
	{
		_a = a;
		_b = b;
		cout << _a << ' ' << b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
	test m1;
	m1.init(1, 2);

	test m2;
	m2.init(3, 4);
	return 0;
}

如以上场景,我们创建了2个对象m1和m2,需要对这2个对象进行初始化,那么每次创建完对象后,就需要调用一次print函数,是不是显得有点麻烦,能不能在我们创建好对象的同时就能进行初始化呢?

下面引出构造函数:

2.2:概念

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

2.3:特点
  • 函数名与类名相同。

  • 无返回值,不需要添加void。

  • 对象实例化的时候自动调用。

  • 可以重载。

2.4:类别

带参的构造函数定义与调用。

class test
{
public:
	test(int a, int b)
	{
		_a = a;
		_b = b;
		cout << _a << ' ' << b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    test t1(5,6);
    return 0;
}

无参的构造函数定义与调用。

class test
{
public:
	test()
	{
		//...
	}
private:
	int _a;
	int _b;
};
int main()
{
    test t1;
    return 0;
}

调用无参的构造函数的时候不要加括号!否则变成函数声明。

2.5:默认构造函数 2.5.1:引出

概念:当用户没有显式定义构造函数的时候,系统会自动生成无参的默认构造函数,一旦显式定义,编译器将不再生成。

class test
{
public:
	/*void test(int a, int b)
	{
		_a = a;
		_b = b;
		cout << _a << ' ' << b << endl;
	}*/  //这边先注释掉
    void print()
    {
        cout << _a << ' ' << _b << endl;
    }
private:
	int _a;
	int _b;
};
int main()
{
    test t1;
    return 0;
}

我们没有定义任何构造函数,创建类类型对象的时候应该编译器会生成默认构造函数帮助我们的_a _b成员变量初始化。

进行测试后发现,这2个成员变量均为随机值,那么问题来了,编译器默认生成的构造函数是不是没有意义?

解答:不是没有意义,在C++的类中,成员变量分为内置类型和自定义类型,在调用默认构造函数的时候,只会对自定义类型初始化,什么意思呢,我们看下面一段代码。

class egg
{
public:
	egg()
    {
		cout << "egg类调用成功" << endl;
	}
};
class test
{
public:
	/*void test(int a, int b)
	{
		_a = a;
		_b = b;
		cout << _a << ' ' << b << endl;
	}*/  //这边先注释掉
	void print()
	{
		cout << _a << ' ' << _b << endl;
	}
private:
	int _a;
	int _b;
	egg _t;
	
};
int main()
{
	test t1;
	return 0;
}

我们再创建一个类egg,然后在test的成员变量里面添加一个egg类的对象_t,那么此时这个t就是自定义变量。

在c++中,创建类类型的对象的时候,**会对其中的自定义类型成员调用它(**自定义类型成员内部的)默认构造函数。

  • 针对内置类型成员变量不初始化问题,C++11补丁添加了:内置类型成员变量可以在类中声明的时候给默认值。
2.5.2:类型

无参数的构造函数

class wjw
{
public:
	wjw()
	{
		cout << a << b << endl;
	}
private:
	int _a;
	int _b;
};

无参数并且参数为缺省值的构造函数

class wjw
{
public:
	wjw(int a = 1, int b = 2)
	{
		cout << a << b << endl;
	}
private:
	int _a;
	int _b;
};

不显式定义构造函数。

class wjw
{
public:
	/*wjw(int a = 1, int b = 2)
	{
		cout << a << b << endl;
	}*/
private:
	int _a;
	int _b;
};
int main()
{
    wjw test;
    return 0;
}

总结:默认构造函数就是没有参数的构造函数。

3:析构函数 3.1:功能

:与构造函数功能相反,在对象销毁的时候自动调用析构函数,完成对象中的资源清理工作。

3.2:特点
  • 在类名前加上~
  • 无参无返回值类型
  • 一个类只有一个析构函数,如果用户没有显式定义析构函数,系统会生成默认的析构函数。(不能重载)
  • 对象生命周期结束时,C++编译器自动调用析构函数。(比如在下面的程序是在return0的过程中对象销毁)
3.3:调用过程

构造函数在类类型对象创建的时候系统会调用,并且会对其中的自定义类型调用它的默认成员函数

对于析构函数,是否也会自动对类中的自定义类型调用它的默认成员函数(这里用析构函数演示)?

#include
using namespace std;
class test1
{
public:
	~test1()
	{
		cout << "调用t1的函数成功" << endl;
	}

};
class test2
{
public:
private:
	int _a;
	int _b;
	test1 _t;
};
int main()
{
	test2 wjw;
	return 0;
}

如图,调用成功。

但是,在main函数中,没有创建test1类的对象,为什么能调用test1类的析构函数?

其实结果是这样:

  • 在main函数中创建了test2类类型对象wjw,wjw中包含3个内置类型成员,最后系统不会对其进行资源销毁,直接进行内存回收即可,而test1类的自定义对象_1是自定义类型的成员变量,所以要调用test1的析构函数,但是在main中不能直接调用test1的析构函数,实际上要释放的是test2类的对象wjw,而在test2类中没有显式定义析构函数,那么编译器会自动在test2类中生成默认析构函数,而在test2类中生成的析构函数目的是为了去调用test1的析构函数,即wjw这个对象销毁的时候,要保证其中的自定义类型的成员也可以正确销毁。
4:拷贝构造函数 4.1:概念

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

4.2:特征
  • 是构造函数的一个重载形式
  • 参数只有一个,并且必须为类类型对象的引用,传值方式编译器直接报错。(传值会无限递归,因为形参是实参的一份临时拷贝)
4.3:浅拷贝

如果没有显式定义拷贝构造函数,编译器会默认生成一个拷贝构造函数,默认的拷贝构造函数会按照内存存储 按字节序完成拷贝,如下代码。

class test1
{
public:
	test1()//构造函数
	{
		_a = (int*)malloc(sizeof(int) * 4);
		if (_a == nullptr)
		{
			perror("malloc fail");
		}
		_b = 4;
	}
	~test1()//默认析构函数
	{
		free(_a);
	}
private:
	int* _a;
	int _b;
};

int main()
{
	test1 wjw;
	test1 zqh(wjw);
	return 0;
}

先创建一个test1类类型对象wjw,test1类中定义2个成员变量,一个指针,一个_b。

再创建一个test1类类型对象zqh,并且拷贝wjw对象,因为我没有在test1类中显式定义拷贝构造函数,所以编译器会自动按字节序拷贝wjw对象,在test1类中我定义了一个析构函数,当return 0的时候会调用这个析构函数。运行结果是这样的。

运行崩溃了!这是为什么?

这是浅拷贝的后果。

在wjw对象创建好的同时,他其中的成员变量_a指针开辟了一块内存空间。

在zqh对象复制wjw的时候,同样也复制了一份与_a指针相同的内存空间。

当return 0这一步的时候,wjw对象和zqh对象都会自动调用析构函数free释放掉_a指向的内存空间,一块内存空间被释放2次,就会造成运行崩溃了。

结论:当涉及内存申请的时候,要自己写构造函数,否则系统默认的浅拷贝构造函数可能会造成同一内存空间的多次释放,造成运行崩溃。

5:赋值运算符重载 5.1:概念

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

  • 不能通过连接其他符号创建新 *** 作符 operator@
  • 重载 *** 作符必须有一个类类型参数
  • 内置类型的运算符,含义不能改变,例如:内置的整形-,不能改变其含义。
  • 作为类成员函数重载的时候,其形参看起来比 *** 作参数少1个,因为有隐藏的参数this。
  • :: 和.* 和sizeof 和 ?: 和.不能重载。
5.2:运算符重载
class date
{
public:
	date(int day, int month, int year)
	{
		_day = day;
		_month = month;
		_year = year;
	}
	bool operator ==(const date& data)//比较是否相等的运算重载
	{
		return _day == data._day
			&& _month == data._month
			&& _year == data._year;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	date test(25,9,2022);
	date test2(24, 9, 2022);
	int k = test == test2;
	cout << k << endl;
	return 0;
}

注意bool函数定义这一块,为什么直接使用内部成员变量==data对象中的_day呢,前面为什么不需要写什么对象._day呢?

这一块前面说到了,运算重载的时候形参第一个默认是隐藏的this指针,指向的是调用该重载函数的类类型对象。

上面的代码表示的test和test2对象的日期是否相同。

5.3:赋值运算重载 5.3.1:特征
  • 参数为const 类名&,引用可以提高传参效率。
  • 检测是否自己给自己赋值
  • 返回值为类名&。有返回值是为了支持连续赋值,返回引用提高返回的效率。
  • 返回*this:要复合连续赋值的含义。
5.3.2:=赋值运算符重载举例
class date
{
public:
	date(int day, int month, int year)
	{
		_day = day;
		_month = month;
		_year = year;
	}
	bool operator ==(const date& data)//比较是否相等的运算重载
	{
		return _day == data._day
			&& _month == data._month
			&& _year == data._year;
	}
	date& operator =(const date& data)//赋值运算重载
	{
		if (this != &data)
		{
			_day = data._day;
			_month = data._month;
			_year = data._year;
		}
		return *this;
	}
	void print()
	{
		printf("%d %d %d\n", _year, _month, _day);
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	date test(25,9,2022);
	test.print();
	date test2(24, 9, 2022);
	test2.print();
	test = test2;//赋值运算重载
	test.print();
	test2.print();
	return 0;
}

看看打印结果

就是将test2对象的日期赋值给test对象。

本篇博客到此结束,感谢阅览!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/3002081.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存