C++:类与对象(最终)

前两篇关于类与对象的博客,都是类与对象中不可或缺的对象,这篇就是在前两篇的基础上,再对类与对象进行补充。

一.简识深浅拷贝

当我们进行拷贝造作函数,或者赋值运算符重载的时候,我们不给出这两个函数,编译器就会默认自动生成,默认对类进行位拷贝(按照基本类型进行值的拷贝)。

那么编译器给的到底有没有问题呢?

看代码:

class Slist
{
public:
	Slist()
	{

	}
	Slist(int size,int cap)
	{
		int* arr = (int*)malloc(sizeof(int)*cap);
		_arr = arr;
		_size = size;
		_cap = cap;
	}
	void PopSlsit(Slist& p,int data)
	{
		p._arr[p._size] = data;
		p._size++;
	}
	void print()
	{
		for (int i = 0; i < _size; i++)
		{
			cout << _arr[i] << endl;
		}
	}
	~Slist()
	{
		if (_arr)
		{
			free(_arr);
			_arr = NULL;
		}
	}
private:
	int* _arr;
	int _size;
	int _cap;
};
int main()
{
	Slist s1(0, 10);
	Slist s2;
	s1.PopSlsit(s1, 1);
	s1.PopSlsit(s1, 2);
	s1.PopSlsit(s1, 3);
	s2 = s1;
	s2.print();
	system("pause");
	return 0;
}

上边的代码能正确的打印出 1,2,3,但是最后就会触发一个断点,为什么会触发一个断点?

 

通过调试,我们看到当s1释放之后,s2的_arr也被释放,所以就是对同一个地址进行了两次释放,所以就会出错。

在拷贝的时候只拷贝地址,而并未复制资源,我们就成之为浅拷贝;

如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源也进行相应复制,这个 过程就可以叫做深拷贝。

如图:

二.类的const成员

1.const修饰普通变量
在C++中,const修饰的变量已经为一个常量,具有宏的属性,即在编译期间,编译器会将const所修饰的常量进行替换。

int main()
{
	const int a = 10;
	a = 30;//错误 error C3892: “a”: 不能给常量赋值	
	system("pause");
	return 0;
}

2.const修饰类成员


(1)const修饰类成员变量时,该成员变量必须在构造函数的初始化列表中初始化


(2) const修饰类成员函数,实际修饰该成员函数隐含的this指针,该成员函数中不能对类的任何成员进行修改
 看下边代码:

class Date
{
public:
	void print()
	{
		_year = 2018;
		_month = 11;
		_day = 1;
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	void print()const//相当于 void print (const Date* this)
	{
		_year = 2018;//出错
		_month = 11;//出错
		_day = 1;//出错
		cout << _year << " " << _month << " " << _day << " " << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.print();

	const Date d2;
	d2.print();
	system("pause");
	return 0;
}

思考题:

1. const对象可以调用非const成员函数和const成员函数吗?

2. 非const对象可以调用非const成员函数和const成员函数吗?

3. const成员函数内可以调用其它的const成员函数和非const成员函数吗?

4. 非const成员函数内可以调用其它的const成员函数和非const成员函数吗? 


class Date
{
public:
	void print()
	{
		_year = 2018;
		_month = 11;
		_day = 1;
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	void print1()const
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	const Date d1;
	//d1.print();//不能通过编译,error C2662: “void Date::print(void)”: 不能将“this”指针从“const Date”转换为“Date &”	
        d1.print1();
	  
	Date d2;
	d2.print();
	d2.print1();
	system("pause");
	return 0;
}
class Date
{
public:
	void print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	void print1()const
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	void test()
	{
		print();
		print1();
	}
	void test()const
	{
		//print();//error C2662: “void Date::print(void)”: 不能将“this”指针从“const Date”转换为“Date &”	
		print1();
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.test();
	const Date d2;
	d2.test();
	system("pause");
	return 0;
}

通过上述的两个代码:

const对象只能调用const修饰的成员函数;

非const对象可以调用const修饰的成员函数,也可以调用非const修饰的成员函数。

在const成员函数内,只能调用const修饰的成员函数

在非const成员函数内,既可以调用const修饰的成员函数,也可以调用非const修饰的成员函数。

三.再谈构造函数

前边说过的构造函数,由于能给成员多次赋值,所以只能称之为赋初值,而不是初始化(初始化只能有一次)。

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)初始化列表只能用于初始化数据成员,数据成员在类中的定义顺序就是初始化列表中的初始化顺序,否则就会出现混乱;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	const int _year;
	int _month;
	int _day;
};
class Date1
{
public:
	Date1(int year, int day, int month)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	const int _year;
	int _month;
	int _day;
};
int main()
{
	Date d(2018,11,2);
	Date1 d1(2018, 11, 2);
	system("pause");
	return 0;
}

 

                   

(3)尽量避免用成员初始化成员,成员的初始化顺序最好和成员的定义顺序一致;

(4)如果类中有以下三个成员,一定要在初始化列表中进行初始化:

           。引用成员变量

           。const成员变量

           。类类型的成员

2.构造函数的作用

(1)构造对象

(2)给对象中成员变量一个初始值

(3)类型转化

对于单参构造函数,可以接受的参数转化成类类型的对象

class Date
{
public:
	 Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d(2018);
	int a = 2019;  //把整形变量给日期类型对象赋值
	d = a;       //实际编译器做一个隐式的类型转化,用2019构造一个无名函数,最后赋值给d
	system("pause");
	return 0;
}

用explicit修饰构造函数,可以抑制这种构造函数的隐式转化。

class Date
{
public:
	explicit Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d(2018);
	int a = 20;
	d = a;             //错误error C2679: 二进制“=”: 没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)

	system("pause");
	return 0;
}

3.默认构造函数

如果一个类未显示定义构造函数,编译器就会合成一个默认的构造函数;如果类显示定义了,编译器就不再合成。

看代码:

class Date
{
public:
	/*Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}*/
	void setdate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	d.setdate(2018, 11, 1);
	system("pause");
	return 0;
}

由于d在创建的时候,是没有任何意义的,完全可以不用合成,合成就需要调用,调用这个构造函数又没有实际的意义,所以编译器就不会自动合成。

d对象创建时,并没有相关的汇编代码。

所以在调用构造函数的时候,编译器会根据自己需求来选择合适构造函数。如果类中没有显示定义构造函数,如果需要编译器会自己默认合成一个构造函数,但这个构造函数一定没有参数。

如下面代码:

class Time
{
public:
	Time(int hour=11, int minute=59, int second=59)
		:_hour(hour)
		, _minute(minute)
		, _second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	void setdate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	Date d;
	d.setdate(2018, 11, 1);
	system("pause");
	return 0;
}

因为在Date类创建当中有一个Time类,编译器在调用Date类的构造函数完成对Date类对象的构造, Date类为显示,所以编译器必须合成:因为Date类中中存在Time类,对Date类构造的时候必须对类中的Time类进行构造,所以编译器必须合成。

四.友元类

友元分成友元函数和友元类

1.友元函数

友元函数可以访问一个类的私有成员,它是定义在类外部的普通函数,在需要时在类中进行声明,在函数面前加friend关键字。

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
public:
	Date()
	{

	}
	Date(int year, int month, int day)
		: _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;
}
int main()
{
	Date d(2018, 11, 1);
	cout << d << endl;
	system("pause");
	return 0;
}

特性:

(1)友元函数可以访问类中的私有成员,但不是类的成员;

(2)友元函数可以在类中任何一个地方进行声明;

(3)友元函数不能有const修饰

(4)一个函数可以是多个类的友元函数

2.友元类

class Time
{
	friend class Date;//友元类的声明
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
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void TimeOfDay(int hour,int minute,int second)//直接访问私有成员
	{
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d(2018, 11, 1);
	d.TimeOfDay(11, 59, 59);
	system("pause");
	return 0;
}

特性:

(1)优点:友元类可以提高代码的效率;

在一个类中访问另一个类的私有成员,原来需要通过调用函数的方法,要花费大量的时间可空间。

(2)缺点:友元类破坏了类的封装性和隐藏性

类中私有的成员本来就是不能被别的类或者函数访问,友元的加入就是的这一种特性失去。

(3)友元类是单向的;

在A类中,用friend加B类进行声明,只能是B类中访问A的私有成员,而A类不能访问B类的私有成员。

(4)友元类没有传递性。

在友元的加持下,A类可以访问B类的私有成员,B类可以访问C类的私有成员,但不代表A类就可以访问C类的私有成员。

五.static成员

先来一个问题:如何知道一个类创建了多少个对象?以下两种方式是否可行,为什么?
(1) 在类中添加一个普通的成员变量进行计数

(2) 使用一个全局变量来计数

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

​class A
{
public:
	A()
	{
		_count++;
	}
	A(const A& d)
	{
		_count++;
	}
	~A()
	{
		--_count;
	}
	static int GetAcount()
	{
		return _count;
	}
private:
	static int _count;
};
void test()
{
	cout << A::GetAcount() << endl;        //0 
	A a1, a2, a3;
	A a4(a3);
	cout << A::GetAcount() << endl;        //4
}
int A::_count = 0;//在类的外部进行初始化
int main()
{
	test();
	system("pause");
	return 0;
}​

特性:

(1)静态成员变必须在类的外部定义,定义是不加static关键字。

(2)静态成员函数没有this指针,不能访问任何非静态的成员。

(3)静态成员会被所有的类共享,不属于某个具体的实例。

(4)静态成员和普通的成员一样。

问题:

1. 静态成员函数可以调用非静态成员函数吗?

class A
{
public:
	A()
	{
		_count++;
	}
	A(const A& d)
	{
		_count++;
	}
	~A()
	{
		--_count;
	}
	static int GetAcount()
	{
		return _count;//错误error C2597: 对非静态成员“A::_count”的非法引用
	}
	int GetACount()
	{
		return _count;
	}
private:
	int _count;
};
void test()
{
	cout << A::GetAcount() << endl;
	A a1, a2, a3;
	A a4(a3);
	cout << A::GetAcount() << endl;
}
int main()
{
	test();
	system("pause");
	return 0;
}

答:静态成员函数是不能调用非静态成员函数的。

2. 非静态成员函数可以调用类的静态成员函数吗? 

​class A
{
public:
	A()
	{
		_count++;
	}
	A(const A& d)
	{
		_count++;
	}
	~A()
	{
		--_count;
	}
	static int GetAcount()
	{
		return _count;
	}
	int GetACount()
	{
		return _count;
	}
private:
	static int _count;
};
void test()
{
	cout << A::GetAcount() << endl;
	A a1, a2, a3;
	A a4(a3);
	cout << A::GetAcount() << endl;
	//cout << A::GetACount() << endl;//错误error C2352: “A::GetACount”: 非静态成员函数的非法调用	

}
int A::_count = 0;
int main()
{
	test();
	system("pause");
	return 0;
}​

答:非静态成员函数不能调用类的静态成员函数。

问题:在不用乘除法,for,while,if,else,switch,case,等关键字以及条件判断语句,完成1+2+3+4+......+n。

我们可以先定义一个类,接着在创建n个这样的类型,然后通过在构造函数函数中完成累加的工作。

class Test
{
public:
	Test()//构造函数
	{
		++n;
		sum += n;
	}
	static int getsum()
	{
		return sum;
	}
private:
	static int sum;
	static int n;
};
int Test::sum = 0;   //类外初始化静态变量
int Test::n = 0;     //类外初始化静态变量
int main()
{
	Test*ptr = new Test[10];//设 n=10
	cout << Test::getsum() << endl;
	delete[]ptr;
	ptr = NULL;
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/oldwang1999/article/details/83623658