C++Day03 类和对象

封装
1.  把变量(属性)和函数(操作)合成一个整体,封装在一个类中
2.  对变量和函数进行访问控制
访问权限
1. 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
2. 在类的外部(作用域范围外),访问权限才有意义:public,private,protected
3. 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时, private和protected是同等级的,外部不允许访问
[struct和class的区别?]
  class默认访问权限为private,struct默认访问权限为public.
将成员变量设置为private
1. 可赋予客户端访问数据的一致性。
如果成员变量不是public,客户端唯一能够访问对象的方法就是通过成员函数。如果类中所有public权限的成员都是函数,客户在访问类成员时只会默认访问函数,不需要考虑访问的成员需不需要添加(),这就省下了许多搔首弄耳的时间。
2. 可细微划分访问控制。
使用成员函数可使得我们对变量的控制处理更加精细。如果我们让所有的成员变量为public,每个人都可以读写它。如果我们设置为private,我们可以实现“不准访问”、“只读访问”、“读写访问”,甚至你可以写出“只写访问”。

设计立方体类(Cube),求出立方体的面积( 2*a*b + 2*a*c + 2*b*c )和体积( a * b * c),分别用全局函数和成员函数判断两个立方体是否相等。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//立方体类
class Cub{
public:
	void setL(int len){ mL = len; }
	void setW(int width) { mW = width; }
	void setH(int height){ mH = height; }
	int getL() const { return mL; } //函数后面加上const表示,函数内部都不会改变成员变量的值
	int getW() const { return mW; }
	int getH() const { return mH; }
	//求面积
	int caculateS(){
		return (mL*mW + mL*mH + mW*mH) * 2;
	}
	//求体积
	int caculateV(){
		return mL*mW*mH;
	}
	//判断两个立方体是否相等
	bool compareCub(const Cub& c2){

		//引用参数加上const,我们就要保证,函数在执行期间不会改变c1
		if (getH() == c2.getH() && getL() == c2.getL() && getW() == c2.getW()){
			return true;
		}
		return false;
	}


private:
	int mL;
	int mW;
	int mH;
};


//c++有bool类型,c <stdbool.h>
bool compareCub(const Cub& c1,const Cub& c2){

	//引用参数加上const,我们就要保证,函数在执行期间不会改变c1
	if (c1.getH() == c2.getH() && c1.getL() == c2.getL() && c1.getW() == c2.getW()){
		return true;
	}
	return false;
}


void test(){

	Cub c1;
	c1.setH(10);
	c1.setL(10);
	c1.setW(10);
	Cub c2;
	c2.setH(10);
	c2.setL(10);
	c2.setW(10);

	cout << "c1的面积:" << c1.caculateS() << " 体积:" << c1.caculateV() << endl;
	cout << "c2的面积:" << c2.caculateS() << " 体积:" << c2.caculateV() << endl;


	//通过全局函数来判断
	if (compareCub(c1,c2)){
		cout << "c1和c2一样大小!" << endl;
	}
	else{
		cout << "c1和c2不一样大小!" << endl;
	}

	//成员函数判断
	if (c2.compareCub(c1)){
		cout << "c1和c2一样大小!" << endl;
	}
	else{
		cout << "c1和c2不一样大小!" << endl;
	}

}

int main(){

	test();

	system("pause");
	return EXIT_SUCCESS;
}



设计一个圆形类(AdvCircle),和一个点类(Point),计算点和圆的关系。
假如圆心坐标为x0, y0, 半径为r,点的坐标为x1, y1.

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;


//点类
class Point{
public:
	//构造函数
	void setX(int x) { mX = x; }
	void setY(int y) { mY = y; }
	int getX() const { return mX; }
	int getY() const { return mY; }
	//析构函数
private:
	int mX;
	int mY;
};

//圆类
class Circle{
public:
	void setCircle(int x,int y){
		mCircle.setX(x);
		mCircle.setY(y);
	}
	void setR(int r){
		mR = r;
	}

	//如果const函数返回引用,必须返回const引用
	const Point& getPoint() const{
		return mCircle;
	}

	const int& getR() const {
		return mR;
	}

#if 0
	//判断点和圆的关系
	void IsInCircle(const Point& p){
	
		//点和圆心距离
		int distance = (p.getX() - mCircle.getX()) * (p.getX() - mCircle.getX()) + (p.getY() - mCircle.getY()) * (p.getY() - mCircle.getY());
		int rdistance = mR * mR;
		if (distance < rdistance){
			cout << "点(" << p.getX() << "," << p.getY() << ")在圆内!" << endl;
		}
		else if (distance > rdistance){
			cout << "点(" << p.getX() << "," << p.getY() << ")在圆外!" << endl;
		}
		else{
			cout << "点(" << p.getX() << "," << p.getY() << ")在圆上!" << endl;
		}

	}
#endif

private:
	Point mCircle; //圆心
	int mR; //半径
};

//全局比较点和圆的关系
//判断点和圆的关系
void IsInCircle(Circle& c,const Point& p){

	//点和圆心距离
	int distance = (p.getX() - c.getPoint().getX()) * (p.getX() - c.getPoint().getX()) + (p.getY() - c.getPoint().getY()) * (p.getY() - c.getPoint().getY());	
	int rdistance = c.getR() * c.getR();
	if (distance < rdistance){
		cout << "点(" << p.getX() << "," << p.getY() << ")在圆内!" << endl;
	}
	else if (distance > rdistance){
		cout << "点(" << p.getX() << "," << p.getY() << ")在圆外!" << endl;
	}
	else{
		cout << "点(" << p.getX() << "," << p.getY() << ")在圆上!" << endl;
	}

}

void test(){
	
	Point p;
	p.setX(10);
	p.setY(10);

	Circle c;
	c.setCircle(10, 0);
	c.setR(5);

	//成员方法
	//c.IsInCircle(p);
	//全局函数
	IsInCircle(c, p);
}


int main(){

	test();

	system("pause");
	return EXIT_SUCCESS;
}

对象的构造和析构
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;


//构造函数自动自动调用,无须手动调用,并且构造函数只会被调用一次,在对象创建的时候
class MyClass{
public:
	//构造函数没有返回值,不能写void,构造函数可以有参数,也就是构造函数可以被重载
	MyClass(){
		cout << "构造函数!" << endl;
	}

	//析构函数,~加上类名,析构函数不能有参数,析构函数没有返回值,不能写void
	~MyClass(){
		cout << "析构函数!" << endl;
	}
};

void test(){
	MyClass m;
}

int main(){

	test();

	system("pause");
	return EXIT_SUCCESS;
}


构造函数的分类及调用
按参数类型:分为无参构造函数和有参构造函数
按类型分类:普通构造函数和拷贝构造函数(复制构造函数)


#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;


//按照参数类型:有参构造函数、无参构造函数
//类型来分:普通构造函数和拷贝构造函数(复制构造函数)

class Person{
public:
	Person(){
		cout << "无参构造函数!" << endl;
	}
	Person(int a){
		age = a;
		cout << "有参构造函数!" << endl;
	}

	//拷贝构造函数
	Person(const Person& p){
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}
	~Person(){
		cout << "析构函数!" << endl;
	}
public:
	int age;
};

//调用无参构造函数
void test01(){
	Person p; //调用无参构造函数
	//调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
	//Person p2();
}


//调用有参的构造函数
void test02(){
	
	//1. 括号法,常用
	Person p1(10);
	

	//2. 显式构造函数
	Person p2 = Person(10);
	Person p3 = 10; // Person p3 = Person(10); 隐式类型转换
	Person p4 = p1; // Person p4 = Person(p1); 
	Person p5(p4);
	//匿名对象 当前行结束之后,马上析构
	//Person(100);

	//Person(p5); //Person p5; 不能调用拷贝构造函数初始化匿名对象
	//如果有变量来接,那么允许


}

int main(){

	//test01();
	test02();

	system("pause");
	return EXIT_SUCCESS;
}

拷贝构造函数的调用时机
对象以值传递的方式传给函数参数
函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
用一个对象初始化另一个对象

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person{
public:
	Person(){
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	Person(int age){
		cout << "有参构造函数!" << endl;
		mAge = age;
	}
	Person(const Person& p){
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}
	//析构函数在释放内存之前调用
	~Person(){
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01(){

	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

	//Person newman3;
	//newman3 = man; //不是调用拷贝构造函数,赋值操作
}

//2. 值传递的方式给函数参数传值
//Person p1 = p;
void doBussiness(Person p1){}
void test02(){
	Person p; //无参构造函数
	doBussiness(p);
}

//3. 以值方式返回局部对象

Person doWork(){
	
	Person p; //无参构造函数
	cout << (int*)&p << endl;
	//返回一个局部对象
	return p; //使用p对象初始化一个匿名对象
}

/*
void doWork(Person& p){

	Person p; //调用构造函数
	cout << (int*)&p << endl;
}

*/

//编译器优化之后,减少一个拷贝构造函数的调用,减少一次析构函数的调用

void test03(){

	Person p = doWork();
	/*
		Person p; //只分配内存
		doWork(p)
	*/
	cout << (int*)&p << endl;
}

int main(){

	//test01();
	//test02();
	test03();


	system("pause");
	return EXIT_SUCCESS;
}


构造函数调用规则
默认情况下,c++编译器至少为我们写的类增加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝
如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造


#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//1. C++编译器默认至少给我们写的类添加三个函数,
//默认构造函数(无参构造函数)、拷贝构造函数、析构函数、(赋值操作符) operator=();
class MyClass{};
void test01(){
	MyClass m1; //编译器提供无参构造函数
	MyClass m2 = m1; //编译器提供了拷贝构造函数
}

//2. 如果你提供了有参构造函数,那么编译器不再提供无参构造函数,但是提供拷贝构造
class MyClass2{
public:
	MyClass2(int a){}
};
void test02(){
	//MyClass2 m1; //提供有参构造函数,编译器不再提供默认构造函数
	MyClass2 m2(10);
	MyClass2 m3 = m2; //编译器提供拷贝构造函数
}

//3. 如果提供拷贝构造,不提供其他构造函数
class MyClass3{
public:
	MyClass3(const MyClass3&){}
public:
	int a;
};
void test03(){

	//MyClass3 m1; //不提供默认构造
}

//编译器提供的默认构造函数,析构函数否是空函数体
//默认拷贝构造函数,进行简单值拷贝

class MyClass4{
public:
	MyClass4(int a){
		mA = a;
	}
public:
	int mA;
};

void test04(){

	MyClass4 m1(100);

	MyClass4 m2 = m1; //调用拷贝构造函数

	cout << "m1.mA:" << m1.mA << endl;
	cout << "m2.mA:" << m2.mA << endl;
}


int main(){

	test04();

	system("pause");
	return EXIT_SUCCESS;
}

深拷贝和浅拷贝
1 浅拷贝
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝.
一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
2 深拷贝
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。


#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person{
public:
	Person(char* name,int age){
		pName = (char*)malloc(strlen(name) + 1);
		strcpy(pName, name);
		mAge = age;
	}

	//手动提供一个拷贝构造函数
	Person(const Person& p){
		//先拷贝年龄
		mAge = p.mAge;
		//再拷贝指针指向空间
		pName = (char*)malloc(strlen(p.pName) + 1);
		strcpy(pName, p.pName);
	}

	~Person(){
		if (NULL != pName){
			free(pName);
			pName = NULL;
		}
	}
public:
	char* pName;
	int mAge;
};

void test01(){
	
	Person p1("Obama",100);
	//没有写拷贝构造的话,使用编译器提供默认的拷贝构造函数,默认拷贝构造函数只是进行简单的值拷贝
	Person p2 = p1;

	cout << p1.mAge << endl;
	cout << p2.mAge << endl;
}

int main(){

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

多个对象构造和析构
1 初始化列表
构造函数和其他函数不同,除了有名字,参数列表,函数体之外还有初始化列表。
初始化列表简单使用:
注意:初始化成员列表(参数列表)只能在构造函数使用。
2 类对象作为成员
在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。
C++中对对象的初始化是非常重要的操作,当创建一个对象的时候,c++编译器必须确保调用了所有子对象的构造函数。如果所有的子对象有默认构造函数,编译器可以自动调用他们。但是如果子对象没有默认的构造函数,或者想指定调用某个构造函数怎么办?
那么是否可以在类的构造函数直接调用子类的属性完成初始化呢?但是如果子类的成员属性是私有的,我们是没有办法访问并完成初始化的。
解决办法非常简单:对于子类调用构造函数,c++为此提供了专门的语法,即构造函数初始化列表。
当调用构造函数时,首先按各对象成员在类定义中的顺序(和参数列表的顺序无关)依次调用它们的构造函数,对这些对象初始化,最后再调用本身的函数体。也就是说,先调用对象成员的构造函数,再调用本身的构造函数。
析构函数和构造函数调用顺序相反,先构造,后析构。


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

class Tractor{
public:
	Tractor(string name){
		cout << "拖拉机构造函数!" << endl;
		//mName = "大拖拉机";
		mName = name;
	}
	~Tractor(){
		cout << "拖拉机析构函数!" << endl;
	}
public:
	string mName;
};

class Car{
public:
	Car(string name){
		cout << "小汽车构造函数!" << endl;
		//mName = "小汽车";
		mName = name;
	}
	~Car(){
		cout << "小汽车析构函数!" << endl;
	}
public:
	string mName;
};

//人
class Person{
public:
	//初始化列表可以告诉编译器调用哪一个构造函数
	//如果不用初始化列表,必须提供默认的构造函数
	//Person() :mTractor("打拖拉机"), mCar("小汽车"){}
	Person(string name1, string name2) : mCar(name2), mTractor(name1){
		cout << "Person 构造函数!" << endl;
	}
	void goWork(){
		cout << "开着" << mCar.mName << "带着" << mTractor.mName << "爬土坡!" << endl;
	}
	~Person(){
		cout << "Person 析构函数!" << endl;
	}
public:
	Tractor mTractor; //拖拉机
	Car mCar; //小汽车
};

void test01(){
	
	Person p("鸵鸟牌","别摸我");
	p.goWork();

}


//初始化列表
class MyClass{
public:
	MyClass(int a, int b, int c) : mA(a),mB(b),mC(c){}
public:
	int mA;
	int mB;
	int mC;
};

int main(){

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

explicit关键字
c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。
[explicit注意]
explicit用于修饰构造函数,防止隐式转化。
是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。


#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//自定义int类型
class MyInt{
public:
	MyInt(int a){
		mA = a;
	}
public:
	int mA;
};

void test01(){
	
	MyInt a = 10; //MyInt a = MyInt(10);

}

class MyString{
public:
	//给字符串指定长度
	 explicit MyString(int n,int a = 10){
	
	}

	MyString(char* str){
	
	}
private:
	char* pStr;
};

void test02(){

	MyString str = 10;
	MyString s(10);
	MyString str2 = "abcd";
}

int main(){



	system("pause");
	return EXIT_SUCCESS;
}


 动态对象创建
当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时,会有这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如果能根据需要来分配空间大小再好不过。
所以动态的意思意味着不确定性。
为了解决这个普遍的编程问题,在运行中可以创建和销毁对象是最基本的要求。当然c早就提供了动态内存分配(dynamic memory allocation),函数malloc和free可以在运行时从堆中分配存储单元。
然而这些函数在c++中不能很好的运行,因为它不能帮我们完成对象的初始化工作。
当创建一个c++对象时会发生两件事:
1. 为对象分配内存
2. 调用构造函数来初始化那块内存
第一步我们能保证实现,需要我们确保第二步一定能发生。c++强迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因。
C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。
现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单。
new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释放内存。正如new表达式返回一个指向对象的指针一样,delete需要一个对象的地址。
delete只适用于由new创建的对象。
如果使用一个由malloc或者calloc或者realloc创建的对象使用delete,这个行为是未定义的。因为大多数new和delete的实现机制都使用了malloc和free,所以很可能没有调用析构函数就释放了内存。
如果正在删除的对象的指针是NULL,将不发生任何事,因此建议在删除指针后,立即把指针赋值为NULL,以免对它删除两次,对一些对象删除两次可能会产生某些问题。
当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化,必须提供一个默认的构造函数。
如果在new表达式中使用[],必须在相应的delete表达式中也使用[].如果在new表达式中不使用[], 一定不要在相应的delete表达式中使用[].


#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person{
public:
	
	Person(){
		cout << "无参构造函数!" << endl;
	}

	Person(int){
		cout << "有参构造函数!" << endl;
	}

	Person(const Person&){
		cout << "拷贝构造函数!" << endl;
	}

	~Person(){
		cout << "析构函数!" << endl;
	}

};

void test01(){

	//Person p; //在栈上
	Person* p1 = new Person;
	//1. 给person在堆上先开辟空间(malloc)
	//2. 调用person构造函数对对象进行初始化操作
	//3. 返回创建好的对象指针

	Person* p2 = new Person(10);
	Person* p3 = new Person(*p2);

	delete p1;
	free(p1);
	//1. 先调用person析构函数
	//2. 释放person对象的内存
}

void test02(){
	void* p1 = new Person;

	//不调用析构函数
	delete p1;
}


//new开辟数组
void test03(){

	int* p = new int[10];
	char* str = new char[64];
	Person* persons = new Person[10];

	delete[] p;
	delete[] str;
	delete[] persons;
}

//对象数组创建注意点
void test04(){
	
	//动态开辟对象数组时候,必须调用默认构造函数
	Person* p = new Person[10];

	//在栈上可以指定数组调用哪个构造函数初始化
	Person p[10]{Person(10),Person(20)};

	delete[] p;

}

int main(){

	//test01();
	//test02();
	test04();


	system("pause");
	return EXIT_SUCCESS;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值