【c++】c++核心编程(2)--类和对象

目录

1.1封装

1.1.2struct和class的区别

1.1.3成员属性设置为私有

1.2对象初始化和处理

1.2.1构造函数和析构函数

1.2.2构造函数的分类及调用

1.2.3拷贝函数的使用

1.2.4构造函数的调用规则

1.2.5浅拷贝和深拷贝

1.2.6初始化

1.2.8静态成员变量和静态成员函数

1.3对象模型和this指针

1.3.1成员变量和成员函数是分开存储的

1.3.2this指针

1.3.3空指针访问成员函数

1.3.4const 修饰成员函数

1.4友元

1.5运算符重载


代码中避免野指针

类:由数据成员定义和成员函数定义构成的一段独立代码

对象:用类定义的存储单元(定义:1.student zhang wang;2.class student {...}zhang,wang;3.class{...}zhang wang;第三个不建议)

内置函数/内联函数(inline)用空间换时间,用函数体替换调用。inline 只有在于代码体在一起是才有用(一般不提倡)

eg:void Foo(int x, int y);

     inline void Foo(int x, int y) // inline 与函数定义体放在一起

面向对象的四大特征:抽象、封装、继承、多态

1.1封装

意义一:设计类的时候,属性和行为写在一起,表现事物

类中的属性和行为被统称为成员

属性---被称为成员属性或者成员变量

行为---被称为成员函数或者成员方法

案例:设计一个圆类,来设计圆的周长。

实例化:通过一个类来创建一个对象的过程

#include<iostream>
using namespace std;
//周长公式:C=2*PI*r
const double PI = 3.14;
//class代表设计一个类,类后面就是类名

class yuan
{
	//访问权限(公共权限)
public:
	//属性(通常为变量)--半径
	int r;
	//行为(通常用函数)--获取圆的周长
	double ZC()
	{
		return 2 * PI*r;
	}
};
//以上为类的声明语句
int main()
{
	//通过圆类,创建一个具体的圆(对象)
//类的定义语句,定义了一个名叫c1的对象
	yuan c1;
    yuan *pc1=c1;
//访问对象成员
    c1.r//“.”读作谁的谁,该语句等价为pc1->c1,“->”读作;谁的指向对象的成员
    
	//给圆进行赋值
	c1.r = 10;
	cout << "c1圆的周长为:" <<c1.ZC()<< endl;
	system("pause");
	return 0;
}

 案例:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。(通过行为给属性赋值)

#include<iostream>
using namespace std;
#include<string>
class student
{
	//权限:
public:
	//属性--姓名--学号
	string name;
	int id;
	//行为--显示学生的姓名和学号
	void show()
	{
		cout << "学号:" << id << "姓名:" << name << endl;
	}
	//通过行为给属性赋值
	void setname(string n)
	{
		name = n;
	}
	void setid(int i)
	{
		id = i;
	}
};
int main()
{
	//通过类,创建一个具体的学生对象
	student s1;
	//s1.id = 21;
	s1.setid(21);
	//s1.name = "张三";
	s1.setname("张三");
	s1.show();
	system("pause");
	return 0;
}

类外定义成员函数:void student::show(){...}(::--->称为预运算符,叫做student的show函数)不建议在类外定义!!!

意义二:在设计类时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  • public--公共权限(成员   类内可以访问,类外也可以访问)
  • protected--保护权限(成员   类内可以访问,类外不可以访问)    儿子可以访问父亲中保护的内容
  • private--私有权限(成员   类内可以访问,类外不可以访问)        儿子不可以访问父亲中保护的内容
class person
{
public:
	string name;
	//保护权限--车子
protected:
	string car;
	//私有权限--银行密码
private:
	int password;
	//类内访问
public:
	void text()
	{
		name = "张三";
		car = "拖拉机";
		password = 12345;
	}

};
int main()
{
	//类外访问
	person p1;
	p1.name = "李四";
	//p1.password;----错,私有类外不可以访问
	system("pause");
	return 0;
}

1.1.2struct和class的区别

在c++中,二者的唯一区别:

默认的访问权限不同

  • struct--默认权限为公有
  • class--默认权限为私有

1.1.3成员属性设置为私有

1、可以自己控制读写权限。

2、对于写,可以检测数据的有效性,防止数据超出有效范围。

案例:立方体案例,利用全局函数和成员函数判断两个立方体是否相等

#include<iostream>
using namespace std;
class cube{
	//行为
public:
	//设置长
	void set_l(int x)
	{
		l = x;
	}
	//获取长
	int getl()
	{
		return l;
	}
	//设置宽
	void set_w(int x)
	{
		w = x;
	}
	//获取宽
	int getw()
	{
		return w;
	}
	//设置高
	void set_h(int x)
	{
		h = x;
	}
	//获取高
	int geth()
	{
		return h;
	}
	//获取立方体面积
	int gets()
	{
		return 2 * w*h + 2 * w*l + 2 * l*h;
	}
	//获取立方体体积
	int getv()
	{
		return h*w*l;
	}
	bool issame(cube &c)
	{
		if (h == c.geth()&&l == c.getl()&&w == c.getw())
			return true;
		else
			return false;
	}
	//属性
private:
	int h;//高
	int w;//宽
	int l;//长
};
bool issame(cube &c1,cube &c2)
{
	if (c1.geth() == c2.geth()&&c1.getl() == c2.getl()&&c1.getw()== c2.getw())
		return true;
	else
		return false;
}
int main()
{
	cube c1;
	c1.set_h(10);
	c1.set_l(10);
	c1.set_w(10);
	cout << "c1立方体的面积为:" << c1.gets() << endl;
	cout << "c1立方体的体积为:" << c1.getv()<< endl;
	cube c2;
	c2.set_h(10);
	c2.set_l(10);
	c2.set_w(10);
	bool n = issame(c1, c2);
	if (n)
	{
		cout << "全局函数:c1和c2相等" << endl;
	}
	else {
		cout << "全局函数:c1和c2不想等" << endl;
	}
	bool x = c1.issame(c2);
	if (n)
	{
		cout << "成员函数:c1和c2相等" << endl;
	}
	else {
		cout << "成员函数:c1和c2不想等" << endl;
	}
	system("pause");
	return 0;
}

1.2对象初始化和处理

1.2.1构造函数析构函数

如果我们不给提供构造函数或者析构函数,那么编译器会自己提供,只不过编译器提供的为空实现。

构造函数语法:类名(){}

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

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

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

 其中p是创建在栈区的一个局部变量,在text运行完,这个变量就会被销毁。

要是代码修改如下,执行结果会是什么样?

int main()
{
	//text();
	person p;
	system("pause");
	return 0;
	
}

运行结果如下:

因为p创建在main函数里,system这行代码会让程序暂停,因此指责个变量还没销毁,因此在随意按任意键之后return之前该变量才会被销毁“析构函数的调用”会一闪而过。 

1.2.2构造函数的分类及调用

分类:

  • 按照参数分类:无参、有参
  • 按照类型分类:普通、拷贝

应用如下:

#include<iostream>
using namespace std;
class person{
	int age;
public:
	person(){
		cout << "无参构造函数的调用" << endl;
	}
	~person()
	{
		cout << "析构函数的调用" << endl;
	}
	//有参
	person(int a)
	{
		age = a;
		cout << "有参构造函数的调用" << endl;
	}
	//拷贝
	person(const person &p)
	{
		age = p.age;
		cout << "拷贝构造函数的调用" << endl;
	}
};
void text()
{
	//括号法:
	/*
	person p1;
	person p2(10);//有参
	person p3(p2);//拷贝
	*/
	//显示法:
	/*
	person p1;
	person p2 = person(10);
	person p3 = person(p2);
	*/
    //隐式转换法:
	person p2 = 10;
	person p3 = p2;
}
int main()
{
	text();
	system("pause");
	return 0;
	
}

注意1:括号法中调用无参构造函数不能加(),因为会被编译器认为是函数的声明。

注意2:显示法中等号右边的person(10)等单独写就是匿名对象,该行执行结束后系统会立即收回匿名对象。

注意3:不能利用拷贝函数初始化匿名对象 eg:person(p2),系统会自动认为person(p2)===person p2

1.2.3拷贝函数的使用

  1. 使用一个已经创建完的初始化一个新的
  2. 值(p)传递的方式传递给一个函数
  3. 以值的方式返回局部变量

拷贝构造函数会创建一个新的对象,不是原来的对象了

1.2.4构造函数的调用规则

默认情况下,c++编译器会给一个类提供3个函数:默认构造函数、默认析构函数、默认拷贝函数。

注意1:如果用户提供了有参构造函数,则编译器不会再提供无参函数,但是还是会提供拷贝函数

注意2:如果用户只提供了拷贝函数,则编译器不会再提供无参构造函数和有参构造函数

1.2.5浅拷贝和深拷贝

析构函数就是将堆区开辟的数据释放

#include<iostream>
using namespace std;
class person{
	
public:
	person(int x,int n)
	{
		cout << "有参构造函数的调用" << endl;
		age = x;
		height = new int(n);
	}
	~person()
	{
		//析构函数就是将堆区开辟的数据释放
		if (height != NULL)
			delete height;
		//delete只是把指针所指的内存释放掉了,并没有改变指针的值,会出现野指针(指向了一个非法的或者已经被销毁,会对系统不利)。
		height = NULL;
		cout << "析构函数的调用" << endl;
	}
	int age;
	int *height;
};
void text()
{
	person p1(18, 180);
	cout << "年龄为:" << p1.age << "身高为:" << *p1.height << endl;
	person p2(p1);
	cout << "年龄为:" << p2.age << "身高为:" << *p2.height << endl;
}
int main()
{
	text();
	system("pause");
	return 0;
}

该代码会报错,那是为什么?

原因如下:栈区是先进后出,所以右边拷贝函数里面的先释放,然后到了左边函数释放的时候该堆区里的数据已经被释放了,所以浅拷贝带来的问题就是堆区的内存重复释放 

所以我们应该再在堆区申请一个内存,代码修改如下:

	person(const person &p)
	{
		age = p.age;
	//	height=p.height       这为原来系统默认的拷贝函数的操作也就是浅拷贝
    //深拷贝
		height=new int(*p.height);//解引用*,就是解释引用即直接去指针所指向地址里的内容,这里也就是180
	}

如果属性有在堆区开放的,一定要自己提供拷贝函数,防止浅拷贝带来的问题!!!

1.2.6初始化

语法:构造函数():属性1(值),属性2(值),属性3(值)...{}

传统初始化如下:

//传统初始化
	person(int a, int b, int c)
	{
		A = a;
		B = b;
		C = c;
	}
//初始化列表法

	person() :A(10), B(20), C(30)
	{
	}

但是,这个初始化是固定的,所以我们可以修改如下:


	person(int a,int b,int c) :A(a), B(b), C(c)
	{
	}

1.2.7

当类中有其他对象作为成员的时候,我们称为对象成员,构造的顺序是先对象成员再类,而析构的顺序和构造的顺序相反

1.2.8静态成员变量和静态成员函数(不属于某一个类)

静态成员变量:

  • 所有对象共享一个数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

静态成员变量的两种访问:1、通过对象2、通过类名

//1、通过对象
	person p;
	p.fun();
//2、通过类名
	person::fun();//person下的fun()函数

静态成员函数:

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量,不可以访问非静态变量(无法确定是谁的)

静态成员函数也是有访问权限的,如果写到了私有里,在类外也是访问不到的

1.3对象模型和this指针

1.3.1成员变量和成员函数是分开存储的

只有非静态成员变量属于类的对象上!!!

class person
{

};
void text1()
{
	person p;
	cout << sizeof(p)<< endl;
}
int main()
{
	
	text1();

	return 0;
}

可以发现运行结果为1,空对象占用内存空间为1(c++为了区分空对象占用内存的位置,每个空对象都有一个独一无二的地址)

如果类里含有char c和int b两个数据输出成员sizeof运行结果是什么?-----8,因为编译器会进行对齐操作

对齐:使CPU的内存访问速度大大提升,使char的字节数变成了

指向成员函数的指针:box *pf;pf=&box::getv;

1.3.2this指针(是隐含定义的):指向当前对象

作用:形参和成员变量名相同时,用来区分。

#include<iostream>
using namespace std;
class person
{
public:
	person(int age)
	{
        //当参数与成员变量名相同时
		age = age;
	}
	int age;
};
void text()
{
	person p(18);
	cout <<p.age<< endl;
}
int main()
{
	
	text();
	getchar();
	getchar();
	return 0;
}

运行结果如图:

 用this指针来区分,代码修改如下,则运行结果为18:

person(int age)
	{
		this->age = age;
	}

this指针的本质是指针常量,指向是不能修改的。person *const this;

this只能在成员函数中使用

this指针不能在静态函数中使用
静态函数如同静态变量一样,他不属于具体的哪一个对象,而this指针却实实在在的对应一个对象,所以this指针不能被静态函数使用。
this指针在成员函数的开始执行前构造的,在成员的执行结束后清除。

this指针只有在成员函数中才有定义。
创建一个对象后,不能通过对象使用this指针。也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置,&this获得,也可以直接使用的)。
this指向p2的指针(被调用成员函数,所属的对象),则*this返回的就是p2这个对象的克隆(如果不引用返回,直接用值的方式返回拷贝函数,会创造新的对象)

1.3.3空指针访问成员函数

如果传入为空指针,应如下操作,防止报错:

person()
	{
		if (this == NULL)
		{
			return;
		}
		cout << this->age << endl;
	}

1.3.4const 修饰成员函数

常函数

void showperson()const相当于const person * const this

函数里的this指针就变成了常量指针常量,则指针指向的值也不能改变。如果还想修改值,在声明变量前加入关键字mutable(在常函数/常对象下都可以修改)再修改值则不会再报错。

常对象

在对象前加const,就是常对象。

常对象只能对用常函数(因为普通的成员函数可以修改值)

1.4友元

1.全局函数做友元

class building
{
	//goodgay是buiding的好朋友,可以访问Buliding里的私有成员
	friend void goodgay(Buliding *buliding);
public:
	Buliding()
	{
		m_sittingroom = "客厅";
		m_bedroom = "卧室";
	}
	string m_sittingroom;
private:
	string m_bedroom;
};
void goodgay(Buliding *buliding)
{
}

可知:friend就是为了告诉编译器,该全局函数是好朋友,可以访问私有成员。

2.类做友元

friend class ***;//表示为:***是本类的好朋友可以访问私有内容。

#include<iostream>
using namespace std;
#include<string>
class Building;
class goodgay{
public:
	goodgay();
	void visit();
private:
	Building *building;
	
};
class Building{
	friend class goodgay;
public:
	Building();
public:
	string m_sittingroom;
private:
	string m_bedroom;
};
//类外写成员函数
//Building类作用域下面的Building
Building::Building(){
	m_sittingroom = "客厅";
	m_bedroom = "卧室";
}
goodgay::goodgay(){
	building = new Building;
	//指针指向堆区的一块内存

}
void goodgay::visit(){
	cout << "正在访问:" << building->m_sittingroom << endl;
	cout << "正在访问:" << building->m_bedroom << endl;
}
int main()

{
	goodgay gg;
	gg.visit();
	system("pause");
	return 0;
}

3.成员函数做友元

friend void goodgay::visit();

1.5运算符重载

1.加号运算符重载(+)

#include<iostream>
using namespace std;
class person{
public:
	
	int m_a;
	int m_b;
	//成员函数重载+号operator--组合
	/*person operator+(person &p)
	{
		person temp;
		temp.m_a = this->m_a + p.m_a;
		temp.m_b = this->m_b + p.m_b;
		return temp;
	}*/
};
//全局函数重载+号
person operator+(person &p1, person &p2)
{
	person temp;
	temp.m_a = p1.m_a + p2.m_a;
	temp.m_b = p1.m_b + p2.m_b;
	return temp;
}
void text()
{
	person p1;
	p1.m_a = 10;
	p1.m_b = 10;
	person p2;
	p2.m_a = 10;
	p2.m_b = 10;
	//成员函数重载本质
	//person p3 = p1.operator+(p2);
	person p3 =p1 + p2;
	//全局函数重载本质
	//person p3=operator+(p1, p2);
	cout << "m_a:" << p3.m_a<<endl;
	cout << "m_b:" << p3.m_b << endl;
}
int main()
{
	text();
	system("pause");
	return 0;
}

2.左移运算符重载(<<)

不用成员函数实现,因为无法实现cout在左侧!!!

#include<iostream>
using namespace std;
class person{
public:
	//成员函数重载,	person operator<<,简化下来为p.operator<<(p<<cout即p在左边)
	int m_a;
	int m_b;
	//
};
//全局函数重载
ostream& operator<<(ostream &cout, person &p)//ostream--标准输出流,cout就是标准输出流这个类创出来的一个对象
//且cout这个对象全局只能有一个,不能创造新的所以要加&。
{
	cout << "m_a :" << p.m_a << "m_b :" << p.m_b << endl;
	return cout;
//要与endl发生连锁,所以返回ostream
}
void text()
{
	person p1;
	p1.m_a = 10;
	p1.m_b = 10;
	//本质:operator<<(cout,p),简化:cout<<p
	cout << p1<<endl;
	
}
int main()
{
	text();
	system("pause");
	return 0;
}

3.递增运算符重载

重载前置++:返回&,为了确保++连锁是对一个值的变化,否则二次++就是对拷贝出来的一个新值进行的操作

重载后置++:返回的不是&,因为引用不能返回局部变量

myintegar operator++(int){}//int代表占位参数,用于区分前置和后置递增

#include<iostream>
using namespace std;
class myintegar{
	friend ostream& operator<<(ostream& cout, myintegar p);
public:
	myintegar()
	{
		m_num = 0;
	}
	//重载前置++:返回&,为了确保++连锁是对一个值的变化,否则二次++就是对拷贝出来的一个新值进行的操作
	myintegar& operator++()
	{
		m_num++;
		return *this;//返回自身
	}
	//重载后置++:返回的不是&,因为引用不能返回局部变量
	myintegar operator++(int)//int代表占位参数,用于区分前置和后置递增
	{
		myintegar temp = *this;
		m_num++;
		return temp;
	}
private:
	int m_num;
};
ostream& operator<<(ostream& cout,myintegar p)
{
	cout << p.m_num << endl;
	return cout;
}
void text()
{
	myintegar num;
	cout << ++num<<endl;
	cout << num++ << endl;
	cout << num << endl;
	
}
int main()
{
	text();
	system("pause");
	return 0;
}

4.赋值运算符重载

属性创建在堆区。当将p1赋值给p2,并且加入析构释放数据,即以下代码,运行则会报错

#include<iostream>
using namespace std;
class person{
public:
	person(int age)
	{
		m_age=new int(age);//将数据开辟到堆区
	}
	~person()
	{
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	}
	int *m_age;

};
void text()
{
	person p1(18);
	person p2(20);
	p2 = p1;
	cout << "age:" << *p1.m_age << endl;
    cout << "age:" << *p2.m_age << endl;
}
int main()
{
	text();
	system("pause");
	return 0;
}

因为编译器给的赋值是浅拷贝操作出现了数据重复释放,需要修改为深拷贝代码修改如下:

//赋值运算符重载
	person& operator=(person&p)
	{
		//编译器:
		//m_age = p.m_age;
		//应该先判断是否存在属性在堆区,若有则释放,然后再深拷贝
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
		m_age=new int(*m_age);//18再开辟一个新的堆区,让自身的这个指针再指向这个堆区
		//返回对象本身,以便于连锁赋值
		return *this;
	}

5.关系运算符重载

bool operator==(person &p)
{
	if (this->m_name == p.m_name&&this->m_age == p.m_age)
	{
		return true;
	}
	return fause;
}

6.函数调用重载

由于重载之后使用的方式类似于函数调用,因此称为仿函数

仿函数没有固定的写法

void operator()(string test)
{
	cout << test << endl;
}

使用方法如下:

void test
{
	myprint p;
	p("hello word");
}

也可以使用如下:

class Myadd{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test()
{
	Myadd myadd;
	int ret=myadd(100,100);
    cout<<"ret="<<ret<<endl;
//匿名函数对象
    cout<<Myadd()(100,100)<<endl;     
}

//匿名函数对象:类加()
   Myadd()(100,100)、 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值