【C++】寒假学习-类和对象

1. C++内存布局分为几个区域,各自具备什么特点?
在C++中,程序在内存中的存储被分为五个区:
1)、栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2)、堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3)、全局/静态区(static):全局变量和静态变量的存储是 放在一块的,在程序编译时分配
4)、文字常量区:存放常量字符串
5)、程序代码区:存放函数体(类的成员函数、全局函数) 的二进制代码

2. 当定义类时,编译器会为类自动生成哪些函数?这些函数各自都有什么特点?
对于一个空类,C++编译器默认生成四个成员函数:默认构造函数、析构函数、拷贝(复制)构造函数、赋值函数。
1)、默认构造函数(default constructor):
在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义。如果定义某个类的变量时没有提供初始化式就会使用默认构造函数。如果用户定义的类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,称为合成的构造函数(synthesized default constructor)。C++语言为类提供的构造函数可自动完成对象的初始化任务。全局对象和静态对象的构造函数在main()函数执行之前就被调用,局部静态对象的构造函数是当程序第一次执行到相应语句时才被调用。然而给出一个外部对象的引用性声明时,并不调用相应的构造函数,因为这个外部对象只是引用在其他地方声明的对象,并没有真正地创建一个对象。
2)、析构函数:
当程序员没有给类创建析构函数,那么系统会在类中自动创建一个析构函数,形式为:~A(){},为类A创建的析构函数。当程序执行完后,系统自动调用自动创建的析构函数,将对象释放。默认的析构函数不能删除new运算符在自由存储器中分配的对象或对象成员。如果类成员占用的空间是在构造函数中动态分配的,我们就必须自定义析构函数,然后显式使用delete运算符来释放构造函数使用new运算符分配的内存,就像销毁普通变量一样。
3)、拷贝构造函数:
如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。
以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。
现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:
a、b.m_data原有的内存没被释放,造成内存泄露;
b、b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;
c、在对象被析构时,m_data被释放了两次。
拷贝构造函数的调用时机:
a. 当把一个已经存在的对象赋值给另一个新对象时,会调用拷贝构造函数。
b. 当实参和形参都是对象,进行实参与形参的结合时,会调用拷贝构造函数。
c. 当函数的返回值是对象,函数调用完成返回时,会调用拷贝构造函数。
4)、赋值函数:
每个类只有一个赋值函数。拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。

3. 什么是浅拷贝, 什么是深拷贝?
浅拷贝是增加了一个指针,指向原来已经存在的内存。而深拷贝是增加了一个指针,并新开辟了一块空间让指针指向这块新开辟的空间。浅拷贝在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误。

  1. 实现一个自定义的String类,保证main函数对正确执行。 
class String
{
public:
	String();
	String(const char *pstr);
	String(const String & rhs);
	String & operator=(const String & rhs);
	~String();

	void print();

private:
	char * _pstr;
};

int main(void)
{
	String str1;
	str1.print();
	
	String str2 = "Hello,world";
	String str3("wangdao");
	
	str2.print();		
	str3.print();	
	
	String str4 = str3;
	str4.print();
	
	str4 = str2;
	str4.print();
	
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "string.h"
#include <iostream>
//#include "stdio.h"

using std::cout;
using std::endl;

//using std::string;
//倾向于将std::string当成一个内置类型使用

class Point
{
public:
	//当没有定义默认构造函数时,编译器会主动提供一个构造函数的作用就是用来初始化数据成员
	//一旦定义了一个有参构造函数时,编译器就不会再主动提供默认构造函数
	//构造函数在执行时,先执行初始化表达式,再执行构造函数执行体,
	//	如果没有在构造函数的初始化列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。
	Point()
		:_ix(0),_iy(0)/*,_iz(0)//合法*/
	{
		//_iz = 0;//违法
		cout << "Point()//默认构造函数" << endl;
	}
	Point(int x,int y)
		:_ix(x), _iy(y)
	{
		cout << "Point(x,y)//构造函数" << endl;
	}
	//每个成员在初始化列表之中只能出现一次,其初始化的顺序不是由成员变量在初始化列表中的顺序决定的,
	//	而是由成员变量在类中被声明时的顺序决定的。
	Point(int val)
		:_iy(val),
		_ix(_iy)
	{
		cout << "Point()//初始化顺序" << endl;
	}
	//拷贝构造函数
	Point(const Point &rhs)
		:_ix(rhs._ix),
		_iy(rhs._iy)
	{
		cout << "Point(const Point &rhs)//拷贝构造函数" << endl;
	}
	//析构函数:清理工作
	~Point()
	{
		cout << "~Point()//析构函数" << endl;
	}
	//在类中定义的非静态成员函数中都有一个隐含的this指针,它代表的就是当前对象本身,
	//	它作为成员函数的第一个参数,由编译器自动补全。
	//	比如print 函数的完整实现是:
	void print(/*Point * const this*/)												//这里的const又是为什么
	{
		cout << "(" << _ix << "," << _iy << ")" << endl;
		//cout << "(" << this->_ix << "," <<this->_iy << ")" << endl;					//为什么编译不了?
	}
private:
	int _ix;
	int _iy;
	//const int _iz;
};

class Line
{
	//在C++ 的类中,有4种比较特殊的数据成员,他们分别是常量成员、引用成员、
	//类对象成员和静态成员,他们的初始化与普通数据成员有所不同。
public:
	Line(int x1, int y1, int x2, int y2)
		:_pt1(x1, y1),
		_pt2(x2, y2)
	{
		cout<<"Line(int, int, int, int)//类对象成员初始化" << endl;
	}

	void print()
	{
		_pt1.print();
		cout << "---->" << endl;
		_pt2.print();
	}
private:
	Point _pt1;
	Point _pt2;
};

class String
{
public:
	String()//默认构造函数
	{
		_pstr=new char[10]();
		strcpy(_pstr, "null");
		cout << "默认构造函数" << endl;
	}
	String(const char *pstr)//构造函数
		:_pstr ( new char[strlen(pstr) + 1]())
	{
		strcpy(_pstr, pstr);
		cout << "构造函数" << endl;
	}
	String(const String & rhs)//拷贝构造函数
		:_pstr(new char[strlen(rhs._pstr) + 1]())
	{
		strcpy(_pstr, rhs._pstr);
		cout << "拷贝构造函数" << endl;
	}
	String & operator=(const String & rhs)//赋值运算符函数
	{
		if (this != &rhs)//排除自复制
		{
			//回收原来的堆空间
			delete[] _pstr;

			_pstr = new char[strlen(rhs._pstr) + 1]();
			strcpy(_pstr, rhs._pstr);//深拷贝
		}
		cout << "赋值运算符函数" << endl;
		return *this;
	}
	~String()//析构函数
	{
		delete[] _pstr;
		cout << "析构函数" << endl;
	}

	void print()
	{
		cout << "str:" << _pstr << endl;
	}

private:
	char * _pstr;
};

class Computer
{
public:
	//这种只拷贝指针的地址的方式,我们称为浅拷贝。当两个对象被销毁时,就会造成double free 的问题。
	Computer(char * brand, double price)							
		:_brand(brand),			//浅拷贝
		_price(price)
	{
		cout << "Computer()//构造函数 浅拷贝" << endl;
	}
	Computer(const char * brand, double price)							//这里为什么要用const?
		:_brand ( new char[strlen(brand) + 1]()),	//深拷贝
		_price(price)
	{
		strcpy(_brand, brand);	
		setTotalPrice();
		cout << "Computer()//构造函数 深拷贝" << endl;
	}
	//系统自动提供一个拷贝构造函数, 固定写法
	//1. 引用符号不能去掉,如果去掉,根据拷贝构造函数的调用时机来看,会导致无穷递归,直到栈溢出,程序崩溃
	//2. const关键字不能去掉,如果去掉,当传递过来的是右值时,就无法正确调用拷贝构造函数
	Computer(const Computer &rhs)										
		:_price(rhs._price),
		 _brand(new char[strlen(rhs._brand) + 1]())
	{
		strcpy(_brand, rhs._brand);
		cout << "Computer(const Computer &rhs)//拷贝构造函数" << endl;
		setTotalPrice();
	}
	Computer & operator=(const Computer &rhs)
	{
		//1.避免自复制
		if (this != &rhs)
		{
			//2.回收原来的堆空间
			delete[] _brand;
			//3.深拷贝
			_brand = new char[strlen(rhs._brand) + 1]();
			strcpy(_brand, rhs._brand);
			cout << "Computer & operator=(const Computer &rhs)//赋值运算符函数" << endl;
		}
		setTotalPrice();
	}

	void setTotalPrice()
	{
		_totalPrice += _price;
	}

	//由于数据成员_brand指向了堆空间的区域,所以必须要显式提供一个析构函数进行回收
	//析构函数要清理的是对象的数据成员申请的资源,而对象本身所占据的空间,不是由析构函数来回收的
	//只要对象被销毁,就会自动调用析构函数
	//不建议显示调用析构函数
	//只有delete表达式才能回收对象占据的空间
	~Computer()
	{
		delete[] _brand;
		cout << "~Computer()" << endl;
	}

	void print()
	{
		cout << "The brand is:" << _brand <<"."<< endl;
		cout << "The price is:" << _price << "." << endl;
		cout << "The totalprice is:" << _totalPrice << "." << endl;
	}
private:
	char *_brand;
	double _price;
	//C++ 允许使用static (静态存储)修饰数据成员,这样的成员在编译时就被创建并
	//	初始化的(与之相比,对象是在运行时被创建的),且其实例只有一个,被所有该
	//	类的对象共享,就像住在同一宿舍里的同学共享一个房间号一样。静态数据成员和
	//	之前介绍的静态变量一样,当程序执行时,该成员已经存在,一直到程序结束,任
	//	何该类对象都可对其进行访问,静态数据成员存储在全局 / 静态区,并不占据对象的
	//	存储空间。
	//一般来说,我们不能在类的内部初始化静态数据成员,必须在类的外部定义和初始化静态数据成员,
	//且不再包含static 关键字,
	static double _totalPrice;
};
//初始化静态数据成员
//类型 类名::变量名 = 初始化表达式; //普通变量
//类型 类名::对象名(构造参数); //对象变量
double Computer::_totalPrice = 0;

int testPoint(void)
{
	Point a;
	a.print();

	Point b(1, 2);
	b.print();

	Point c(3);
	c.print();

	Point d = b;
	d.print();

	return 0;
}

int testLine(void)
{
	Line line(1, 2, 3, 4);
	line.print();

	return 0;
}

int testString(void)
{
	String str1;
	str1.print();

	String str2 = "Hello,world";
	str2.print();

	String str3("wangdao");
	str3.print();

	String str4 = str3;		//拷贝构造函数
	str4.print();

	str4 = str2;				//赋值运算符函数
	str4.print();

	//system("pause");
	return 0;
}

int testComputer(void)
{
	//char brand[] = "Alien";
	//Computer com1(brand, 20000);
	//com1.print();
	//strcpy(brand, "change");
	//com1.print();

	//栈对象
	const char brand1[] = "Alien";		
	Computer com2(brand1, 20000);
	com2.print();
	
	//堆对象
	Computer* com3=new Computer("Macbook pro", 20000);
	com3->print();

	//main函数退出之后,静态对象会自动被销毁
	static Computer com4("Xiaomi", 7777);
	com4.print();

	Computer com5 = com4;
	com5.print();

	cout << endl;
	cout << "testComputer() over..." << endl;
	return 0;																	//在这里打断点只有一个析构函数,为什么在return的时候出错误了?
}
//main函数退出之后,全局对象会自动被销毁
//Computer com6("Thinkpad", 8888);

int main(void)
{
	//testPoint();

	//testLine();

	//testString();


	testComputer();
	//cout << endl;
	//cout << "return to main()..." << endl;
	//cout << endl;
	//com6.print();										//退出为什么只有一个析构函数?

	system("pause");
	return 0;
}

特殊数据成员的初始化
常量数据成员const、引用成员、类对象成员、静态成员
1.常量成员只能在构造函数初始化列表中初始化
2.引用成员也只能在构造函数初始化列表中初始化
3.类对象成员也只能在构造函数初始化列表中初始化
4.静态成员初始化

发布了43 篇原创文章 · 获赞 4 · 访问量 1200

猜你喜欢

转载自blog.csdn.net/weixin_42176221/article/details/103829758
今日推荐