【c++】类和对象基础(拷贝函数、构造函数、深拷贝、浅拷贝、对象成员、this指针、const修饰函数和对象、友元)

本文介绍了C++中的面向对象编程特性,包括类的创建、赋值和权限控制。详细讲解了public、protected、private的访问权限,struct与class的区别,以及封装的概念和优点。此外,还阐述了构造函数和析构函数的作用,无参构造、有参构造和拷贝构造的调用规则,并讨论了深拷贝与浅拷贝的区别。最后,提到了静态成员、this指针、常量对象和友元的相关知识。

类的创建、赋值、权限

C++面向对象的三大特性为:封装、继承、多态
语法:class 类名{ 访问权限: 属性 / 行为 };

访问权限
公共权限 public 成员 类内可以访问,类外可以访问
保护权限 protected 成员 类内可以访问,类外不可访问 子类可以访问父类中的保护内容
私有权限 private 成员 类内可以访问,类外不可访问 子类不以访问父类中的私有内容

stuct和class的比较
在C++中 struct和class唯一的区别就在于默认的访问权限不同

  		struct 默认权限为公共
  		class  默认权限为私有

封装:成员属性设置为私有
优点:将所有成员属性设置为私有,可以自己控制读写权限
对于写权限,我们可以检测数据的有效性

注意:同一个项目下结构体之间,类之间,结构体和类之间的命名都要不同

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

class stu {
public:
	//属性  成员属性  成员变量
	string sName;
	int sId;

	//行为  成员函数  成员方法
	void set_sName(string name) {
		sName = name;
	}
	void set_sId(int id) {
		sId = id;
	}
	void showprint() {
		cout << "name:" << sName << "\tid:" << sId << endl;
	}
};

class tea {
	//属性
public:
	string tName;
protected:
	int tId;
private:
	int tWage;

	//方法
public:
	void set_tea(string name, int id, int wage) {
		this->tName = name;
		this->tId = id;
		this->tWage = wage;
	}
	void showprint() {
		cout << "name:" << tName << "\tid:" << tId << "\t\twage:" << tWage << endl;
	}
};

struct test1 {
	int struct_test = 10;//struct 默认权限为公共
};

class test2 {
	int class_test = 10;//class  默认权限为私有
};

void main() {
	stu tony;
	//public类可以这样赋值
	//tony.sName = "tony";
	//tony.sId = 123;

	tony.set_sName("tony");
	tony.set_sId(123);
	tony.showprint();

	tea mary;
	mary.set_tea("mary",101,1000);
	mary.showprint();
	

	test1 a;
	a.struct_test = 0;//可访问
	test2 b;
	//b.struct_test = 0;//不可访问
}

类和对象:初始化和清理

构造函数:类名(){数据类型 参数}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数:~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

如果不提供构造函数和析构函数,编译器会提供空实现的构造函数和析构函数

class Person {
public://构造函数和析构函数默认是私有的,所以要加public
	Person(){//程序在调用对象时候会自动调用构造
		cout << "我是Person的构造函数" << endl;
	}

	~Person() {//程序在对象销毁前会自动调用析构
		cout << "我是Person的析构函数" << endl;
	}
};

void test(){
	Person p;
}

void main(){
	test();
	system("pause");
	Person person;
	system("pause");
}

构造函数的分类及调用

两种分类方式:
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造

三种调用方式:
括号法
显示法
隐式转换法

注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明(返回类型 函数名(),Person p()——错误;)
注意2:不能利用拷贝构造函数初始化匿名对象编译器认为是对象声明

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	int score;

	//普通构造
	Person() {//无参构造
		cout << "无参构造函数!" << endl;
		this->age = 0;
		this->score = 0;		
	}

	Person(int age, int score) {//有参构造
		cout << "有参构造函数!" << endl;
		this->age = age;
		this->score = score;		
	}

	//拷贝构造
	Person(const Person  &p) {
		cout << "拷贝构造函数!" << endl;
		this->age = p.age;
		this->score = p.score;
	}

	void Show() {
		cout << "age:" << age << "\tscore:" << score << endl;
	}

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

//调用有参的构造函数
void test() {

	//括号法,常用
	cout << "*****括号法,常用*****" << endl;
	Person p11; 
	p11.Show();
	Person p12(20,80);
	p12.Show();
	Person p13(p12);
	p13.Show();
	//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明——返回类型  函数名(),Person p()——错误;

	//显式法
	cout << endl << "*****显式法*****" << endl;
	Person p21 = Person();
	p21.Show();
	Person p22 = Person(20,80);
	p22.Show();
	Person p23 = Person(p22);
	p23.Show();
	//Person(10)单独写就是匿名对象  当前行结束之后,马上析构

	//隐式转换法
	cout << endl << "*****隐式转换法*****" << endl;
	Person p31 = {  };//元素为空或者有多个元素时要用{ },只有一个元素时可以不用{ }直接写元素
	p31.Show();
	Person p32 = { 10, 80 };
	p32.Show();
	Person p33 = p32; 
	p33.Show();

	//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
	//Person p5(p4);
}

void main() {
	test();	
}

拷贝构造函数的调用时机

1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.以值方式返回局部对象

#include<iostream>
using namespace std;

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

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test_a() {
	cout << "*****1.使用一个已经创建完毕的对象来初始化一个新对象" << endl;
	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

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

//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test_b() {
	cout << endl << "*****2.值传递的方式给函数参数传值" << endl;
	Person p; //无参构造函数
	doWork(p);
}

//3. 以值方式返回局部对象
Person doWork2(){
	Person p1;
	cout << (int*)&p1 << endl;
	return p1;
}

void test_c(){
	cout << endl << "*****3.以值方式返回局部对象" << endl;
	Person p = doWork2();
	cout << (int*)&p << endl;
}

void main() {
	test_a();
	test_b;
	test_c();
}

构造函数调用规则

创建一个类时,默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数(无参构造,有参构造,构造函数)

#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 test_a()
{
	Person p1(18);
	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
	Person p2(p1);

	cout << "p2的年龄为: " << p2.age << endl;
}

void test_b()
{
	//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
	Person p1; //此时如果用户自己没有提供默认构造,会出错
	Person p2(10); //用户提供的有参
	Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供

	//如果用户提供拷贝构造,编译器不会提供其他构造函数
	Person p4; //此时如果用户自己没有提供默认构造,会出错
	Person p5(10); //此时如果用户自己没有提供有参,会出错
	Person p6(p5); //用户自己提供拷贝构造
}

void main() {
	test_a();
	test_b();
}

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
不管对象的属性是什么数据类型,都是直接拷贝一份,如果是指针的话,其实只拷贝了地址,并没有复制数据
对于指针,直接复制指针,没有复制数据(两个对象中的height指针存放的地址相同,指向同一个内存空间)
深拷贝:在堆区重新申请空间,进行拷贝操作
对于指针,先在堆区域复制一个原对象指针指向的数据,再将新对象的指针指向该数据(两个对象中的height指针存放的地址不同,但两个地址的数据相同)

对象中成员含指针时,建议用深拷贝

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	int *height;

	Person() {
		cout << "无参构造" << endl;
	}

	Person(int age, int height) {
		cout << "有参构造" << endl;
		this->age = age;
		this->height = new int(height);//new是在堆区重新开辟一个空间,并返回的是该数据类型的指针
	}

	Person(const Person &p) {
		cout << "拷贝构造" << endl;
		this->age = p.age;
		
		//浅拷贝,对于指针,直接复制指针,没有复制数据(两个对象中的height指针存放的地址相同,指向同一个内存空间)
		//this->height = p.height;//用浅拷贝的时候,在两个对象的指针都释放的时候会报错
		
		//深拷贝,对于指针,先在堆区域复制一个原对象指针指向的数据,再将新对象的指针指向该数据(两个对象中的height指针存放的地址不同,但两个地址的数据相同)
		this->height = new int(*p.height);//用深拷贝时,对象含指针释放的时候也不会报错
	}

	~Person() {
		cout << "析构函数!" << endl;//析构函数将堆区域的变量释放
		if (height != NULL)
		{
			delete height;
			height = NULL;//置空,防止野指针出现
		}
	}
};

void test() {
	Person person(24, 180);
	cout << "person\t\tage:" << person.age << "\thegiht" << *person.height << endl;
	Person new_person(person);
	cout << "new_person\tage:" << person.age << "\thegiht" << *person.height << endl;
}

void main() {
	test();
}

初始对象

方法一:利用构造函数,写在构造函数里面,一个一个成员的赋值
方法二:构造函数():属性1(值1),属性2(值2)… {}

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	int height;
	int score;
	Person(int a,int b,int c) :age(a), height(b), score(c) {//方法二初始化列表
	}
	void showprint() {
		cout << "age:" << age << "\t\theight:" << height << "\tscore:" << score << endl;
	}
};

void main() {
	Person person(20,180,85);
	person.showprint();
}

对象成员

类作为另一个类的成员时,我们称该成员为对象成员
构造的顺序是 :先调用对象成员的构造,再调用本类构造
析构顺序与构造相反

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

class Phone {
public:
	string pname;
	int price;
	Phone() {}
	Phone(string a, int b) :pname(a), price(b) {
		cout << "phone构造函数" << endl;
	}
};

class Person {
public:
	string name;
	Phone myphone;
	Person(string a, string b, int c) :name(a), myphone(b, c) {//这里myphone(b, c)是一个隐式转换法,等价Phone myphone={b,c}
		cout << "person构造函数" << endl;
	}
};

void test() {
	Person person("张三", "华为", 3200);
	cout << "name:" << person.name << "\t\tphone:" << person.myphone.pname << "\t\tprice:" << person.myphone.price << endl;
}

void main() {
	test();
}

静态成员,静态方法

静态成员变量
1.所有对象共享同一份数据
2.在编译阶段分配内存
3.类内声明,类外初始化(重点)

静态成员函数
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量

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

class Person {
public:
	static int static_a;
	int nostatic;
	static void fuc_a() {
		cout << "静态方法fuc_a(puplic)" << endl;
		//nostatic = 100;//静态函数只能访问静态成员,非静态成员的静态函数不能访问
	}

private:
	static int static_b;
	static void fuc_b() {
		cout << "静态方法fuc_b(private)" << endl;
	}
};

int Person::static_a = 0;
int Person::static_b = 0;

void test() {
	//静态成员变量和静态成员方法两种访问方式
	//1、通过对象
	Person person;
	person.static_a = 100;
	//person.static_b = 100;//私有的静态成员不可访问
	cout << "static_a = " << person.static_a << endl;
	person.fuc_a();
	//2、通过类名
	cout << "static_a = " << Person::static_a << endl;
	Person::fuc_a();
	//Person::fuc_b();//私有的静态方法不可访问
}

void main() {
	test();
}

类和对象的大小

C++中类内的成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上、空类1B,非空类(静态成员变量的大小相加)

#include<iostream>
using namespace std;

class Person_a {};//1B

class Person_b {//4B
	int a;//非静态成员变量
};

class Person_c {//4B
	int a;//非静态成员变量
	static int static_c;//静态成员变量
};
int Person_c::static_c = 0;

class Person_d {//4B
	int a;//非静态成员变量
	static int static_d;//静态成员变量
	void func_() {}
};

void test() {
	//编译器会给空对象分配一个字节空间,为了区分空对象占内存的位置
	cout << "空类占字节数:" << sizeof(Person_a) << endl;
	//非静态成员变量  属于类的对象上
	cout << "(只含非静态成员变量)类占字节数:" << sizeof(Person_b) << endl;
	//静态成员变量    不属于类的对象上
	cout << "(非静态成员变量+静态成员变量)类占字节数:" << sizeof(Person_c) << endl;
	//成员函数        不属于类的对象上
	cout << "(非静态成员变量+静态成员变量+函数)类占字节数:" << sizeof(Person_d) << endl;
}

void main() {
	test();
}

this指针

this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可

this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

const修饰函数和对象

常函数
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象
声明对象前加const称该对象为常对象
常对象只能调用常函数

#include<iostream>
using namespace std;

class Person {
public:
public:
	int m_A;
	mutable int m_B; //可修改 可变的

	Person() {
		m_A = 0;
		m_B = 0;
	}

	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	void ShowPerson() const {
		//const Type* const pointer;
		//this = NULL; //不能修改指针的指向 Person* const this;
		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的

		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		this->m_B = 100;
		cout << "常函数" << endl;
	}

	void MyFunc() {
		//mA = 10000;
		cout << "普通函数"<< endl;
	}
	//const修饰对象  常对象
};
void test() {
		const Person person; //常量对象  
		cout << person.m_A << endl;
		//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
		person.m_B = 100; //但是常对象可以修改mutable修饰成员变量

		person.ShowPerson();//常对象只能调用常函数

		//常对象访问成员函数
		//person.MyFunc(); //常对象不能调用普通函数
		Person person11;
}
void main() {
	test();
}

友元

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 friend

友元的三种实现
1.全局函数做友元 friend void myfriend();
2.类做友元 friend class friend_a;
3.成员函数做友元 friend void friend_b::visit1();

注意: 1.用指针(或引用)创建类,可以解决类未定义的问题(先声明)
2.把类中所有的函数先声明,放在最后定义,可以解决未定义的问题

#include<string>
using namespace std;
class Building;
class friend_a;
class friend_b;

class friend_a {//友元类
public:
	friend_a();
	void visit();
private:
	Building *b;
};

class friend_b {//友元函数的类
public:
	friend_b();
	void visit1();//友元函数
	void visit2();//非友元函数
private:
	Building *b;
};


class Building {//本类
	//1.全局函数做友元,告诉编译器 myfriend全局函数是 Building类的友元,可以访问类中的私有内容
	friend void myfriend();
	//2.类做友元,友元类内的函数可以访问该类的私有容
	friend class friend_a;
	//3.成员函数做友元,该成员函数可以访问该类的私有容
	friend void friend_b::visit1();
public:
	string sittingroom;
private:
	string bedroom;
public:
	Building();
};

friend_a::friend_a() {
	b = new Building;
}

friend_b::friend_b() {
	b = new Building;
}
Building::Building() {
	sittingroom = "客厅";
	bedroom = "卧室";
}
void friend_a::visit() {//2.类做友元
	cout << "我的朋友正在访问:" << b->sittingroom << endl;
	cout << "我的朋友正在访问:" << b->bedroom << endl;//friend_类是 Building类的友元,可以访问类中的私有内容
}

void friend_b::visit1() {
	cout << "我的朋友正在访问:" << b->sittingroom << endl;
	cout << "我的朋友正在访问:" << b->bedroom << endl;//friend_b::visit1()成员函数是Building类的友元,可以访问类中的私有内容
}
void friend_b::visit2() {
	cout << "我的朋友正在访问:" << b->sittingroom << endl;
	//cout << "我的朋友正在访问:" << b->bedroom << endl;//friend_b::visit2()成员函数不是Building类的友元,不可以访问类中的私有内容
}


void myfriend() {//1.全局函数做友元
	Building see;
	cout << "我的朋友正在访问:" << see.sittingroom << endl;
	cout << "我的朋友正在访问:" << see.bedroom << endl;//myfriend全局函数是 Building类的友元,可以访问类中的私有内容
}


void main() {
	myfriend();
	friend_a fria;
	fria.visit();
	friend_b frib;
	frib.visit1();
	frib.visit2();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值