c++补充

##第六章、引用
给变量起别名

一、基本语法

  • 数据类型 &别名 = 原名
  • int &b = a
  • 若修改b的值,那么a的值也会变化

二、注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变
  • 初始化为一个变量的别名后,不可再改为其他变量的别名

三、引用做函数参数

  • 作用:函数传参时,可以利用引用让形参修饰实参
  • 优点:可以简化指针修饰实参

实例

int myswap(int &a,int &b)
{
	int temp = a;
	a=b;
	b=temp;
}

这样在main函数中引用此函数,形参就会修饰实参了

swap中的a与b是别名,那么swap函数中对a与b的操作就是在对实参操作

四、引用函数做返回值

  • 作用:引用可以作为函数返回值存在
  • 注意:不要返回局部变量引用

实例

int& test01()
{
	int a=10;

	return a;
}
int main()
{
	int& ref=test01;

	cout<<ref<<endl;

	cout<<ref<<endl;
}

这样,第一次输出ref的值时是正常的a,因为编译器做了暂时的保留,而第二次输出则为乱码,因为a在test01中执行完操作就被释放了

  • 用法:函数调用可以作为左值

实例

int& test02()
{
	static int a=10;
	return a;
}
int main()
{
	int& ref2=test02;

	cout<<ref2<<endl;

	test02 = 1000;//如果函数的返回值是引用,这个函数的调用可以作为左值进行赋值

	cout<<ref2<<endl;
}

有“static”那么a就是静态变量,只有在整个程序结束后才会被系统释放
第一个ref2输出为10,第二个ref2的输出为1000

五、引用的本质

  • 本质:引用的本质在c++内部实现是一个指针常量
  • 编译器发现引用后,就转换为 int * const ref = &a;

六、常量引用

  • 作用:常量引用主要用来修饰形参,防止误操作
  • 在函数形参列表中,可以加const修饰形参,防止形参改变实参

const int & ref = 10;

这样编译器就自动修改为

int temp = 10;
int & ref = temp;

而写int & ref=10;是错误的,并且加入const之后ref不可修改了

void a(const int &val)

这样在函数体内部或main
函数内部就不会对val进行误操作

##类和对象
面向对象三大特性:封装、继承、多态
###一、封装

1、意义:

  • 将属性和行为作为一个整体
  • 将属性和行为加以权限控制

2、语法:

  • class 类名{访问权限: 属性/行为};

3、访问权限:

  • public 公共权限 成员 类内可以访问,类外可以访问
  • protected 保护权限 成员 类内可以访问,类外不可访问
  • private 私有权限 成员 类内可以访问,类外不可访问

4、struct和class的区别

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

5、成员属性设为私有

  • 可以自己控制读写权限
  • 对于写,可以检测数据的有效性
  • 可以再定义一个public内写一个函数来修改private内的内容

6、成员函数判断

  • 定义一个判断的函数
  • 该函数传入参数为一个未知的如传入&c(即未定义的)
  • bool ret = c1.函数(c2);

7、全局函数判断

  • 传入两个成员
  • 直接判断
  • bool ret=函数(c1,c2);

8、分文件编写

  • 头文件中只写函数的声明以及属性的声明
  • 源文件中注意:加上在谁的的成员函数下
  • point::
  • 这样在主源文件下只需传入数据并进行判断

二、对象的初始化和清理

1、构造函数和析构函数

注:均在public下写

i、构造函数

实现初始化操作

  • 主要作用于创建对象时为对象的成员赋值,构造函数由编译器自动调用,无需手动调用
  • 语法:类名(){}
  • 没有返回值也不写void
  • 函数名称与类名称相同
  • 函数名称与类名相同
  • 构造函数可以有参数,因此可以发生重载
  • 只会调用一次

ii、析构函数

实现清理操作

  • 主要作用于对象销毁前系统的自动调用,执行一些清理工作
  • 语法:~类名(){}
  • 没有返回值也不写void
  • 函数名称与类名称相同,在名称前加上 ~
  • 析构函数不可以有参数,因此不可以发生重载
  • 程序在对象销毁前会自动调用析构,无需手动,且只调用一次

构造和析构都是必须有的实现,若自己不提供,编译器会自动提供一个空实现的构造和析构

class person(){
public:
	person(){
		cout<<"111"<<endl;
	}
	~person(){
		cout<<"222"<<endl;
}
void test(){
	person p;  //创建对象
			   //test01执行完毕后会释放这个对象
}
int main(){
	test();

	system("pause");
	return 0;
}

上面代码会输出

111
222

person p;写在main函数内则会在system("pause");之后才会输出析构函数的内容

2、构造函数的分类及调用

i、分类方式

  • 按参数分为:有参构造和无参构造
  • 有参:person(int a){}
  • 无参:person(){}
  • 按类型分为:普通构造和拷贝构造
  • 拷贝构造:person(const person &p){age = p.age;}将传入的人身上的所有属性,拷贝到我身上

ii、调用方式

在一个void函数下

括号法

  • person p1;//默认构造函数调用
  • person p2(10);//有参构造函数
  • person p3(p2);//拷贝构造函数

注:调用默认构造函数时,不要加(),因为编译器会认为person p1();是一个函数的声明

显示法

  • person p2 = person(10);//有参构造
  • person p3 = person(p2);//拷贝构造
  • person(10)为匿名对象,当前执行结束后,系统会 立即 回收匿名对象

注:不要利用拷贝构造函数来初始化匿名对象。如person(p3);,编译器会认为person (p3)等价于person p3

  • 隐式转换法
  • person p4 = 10;//相当于person p4 = person(10); 有参构造
  • perosn p5 = p4;//拷贝构造
3、拷贝构造函数调用时机

i、使用一个已经创建完毕的对象来初始化一个对象

void test01(){
	person p1(20);
	person p2(p1);
}

ii、值传递的方式给函数参数传值

void dowork(person p){

}
void test02(){
	person p;
	dowork (p);
}

实参在传给形参时,会调用一个拷贝构造函数,在dowork中对p进行的操作不会影响test02中的p

iii、值方式返回局部对象

person dowork2(){
	person p1;
	return p1;
}
void test03(){
	person p = dowork2();

}

只返回值,不返回地址(地址被释放)

4、构造函数的调用规则
  • 创建一个类,编译器会自动给出默认构造,默认析构,默认拷贝
  • 如果定义有参构造函数,c++不再提供默认无参构造(此时会报错,必须要写一个默认构造),但会提供默认拷贝构造
  • 如果定义拷贝构造函数,c++不会再提供其他构造函数(此时也会报错,必须要写默认构造和默认析构)
5、浅拷贝和深拷贝
  • 简单的复制拷贝工作

在堆区申请一段内存:如new int(height);
注:要及时释放这一段内存(用delete)

析构代码

if(m_height != NULL){
	delete m_height;
	m_height = NULL;
  • 不足:会导致堆区内存重复释放
  • 在堆区重新申请空间,进行拷贝工作

person(const person &p){
	m_age=p.m_age;
	m_height = new int(*p.m_height);
6 、初始化列表
  • 语法:构造函数():属性1(值1),属性2(值2)…{}
  • 如person(int a,int b):A(a),B(b){}
7、类对象作为类成员

c++类中的成员可以是另一个类的对象,我们称该成员为对象成员

class phone{
public:
	phone(string pname){
		m_name = pname;
		
	}
	string m_pname;
}

class person{
public:
	person(string name,string pname):m_name(name),m_phone(pname){}
	string m_name;
	phone m_phone;//在person类中使用了phone类
}

构造顺序 先有手机再有人,析构顺序 先释放人再释放手机。二者顺序相反

8、静态成员

加上关键字static

i、静态成员变量(也有访问权限,私有的类外访问不到)

  • 所有对象共享同一份数据

访问方式1:

person p;
cout<<p.m_A<<endl;

访问方式2:

cout<<person::m_A<<endl;
  • 在编译阶段分配内存
  • 类内声明(static int m_A;),类外初始化(int person::m_A = 100;)这样就可以在函数中访问到了

ii、静态成员函数(也有访问权限,私有的类外访问不到)

  • 所有对象共享同一个函数

访问方式(通过对象)1:

person p;
p.func();

访问方式(通过类名)2:

person::func();
  • 静态成员函数只能访问静态成员变量
  • 没法区分是哪个对象的m_B

三、c++对象模型和this指针

1、成员变量和成员函数分开存储

i、

  • 空对象占用的空间为:1
  • 是为了区分空对象占内存的位置
  • 一个非静态成员变量占用内存为:4,而成员变量和成员函数是分开存储的,所以将一个函数与一个成员变量写进同一个类内输出大小还是4
  • 静态的成员变量 不属于类对象上,同理成员函数

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

2、this指针概念
  • this指针指向被调用的成员函数所属的对象(谁调用就指向谁)
  • 无需定义,直接使用
  • 相当于 类名 * const this;
  • 指向不可以修改

用途

  • 当形参和成员变量同名时,可用this指针来区分

class Person{
public:
	Person(int age){
		//this指针指向被调用的成员函数所属的对象,指 向的是p1
		this->age = age;
	}
	Person& addage(person &p){
		this->age += p.age;
		return *this;
	}
int age;
}
void test01(){
	Person p1(18);

}
void test02(){
	Person p1(10);
	Person p2(10);
	p2.addage(p1).addage(p1).addage(p1);
	//链式编程
}

这样就可以区分形参和成员变量

  • 在类的非静态成员函数中返回对象本身,可用return *this
3、空指针访问成员函数
  • 空指针person *p=NULL;
  • 空指针不可以访问没定义的属性
4、const修饰成员函数
  • 常函数
    * 成员函数后加const,相当于使this指针指向的值变得不可修改
    * 常函数不可以修改成员属性
    * 成员属性声明时加关键字mutable后,在常函数中依然可以修改
  • 常对象
    * 声明对象前加const
    * 常对象只能调用常函数(因为普通成员函数可以修改属性)

四、友元

让类外特殊的一些函数或类进行访问

  • 关键字为frend
三种实现
  • 全局函数做友元
`frend void func();`
  • 类做友元
`friend class 类();`

类外写成员函数:

先在类内进行声明

类名::函数名(){
	函数体;
}

对于有返回值类型的函数要加上返回值类型

  • 成员函数做友元
`friend void 类名::函数名();`

五、运算符重载

对已有的运算符进行重新定义,赋予其另一种功能

一、加号运算符重载

实现两个自定义数据相加的运算

  • 函数名:operator+
  • 通过成员函数重载+号(写在一个类下面)

Person operator+(Person &p){
	Person temp;
	temp.A=this->A+p.A;
	temp.B=this->B+p.B;
	return temp;
}

调用时可以Person p3=p1+p2;
本质为Person p3 = p1.operator+(p2);

  • 全局函数重载+号(写在类外)

person operator+(Person &p1,Person &p2){
	person temp;
	temp.A=p1.A+p2.A;
	temp.B=p1.B+p2.B;
	return temp;
}

调用时可以person p3=p1+p2;
本质为person p3 = operator+(p1,p2);

其实还可以实现person类与int型进行相加,只需在函数相应的位置修改成对应的数据类型,就可以实现如p4= p1 + 10;这样的运算

二、左移运算符重载

可以输出自定义数据类型
不会利用成员函数重载<<运算符,无法实现cout在左侧

  • 利用全局函数

ostream &operator<<(ostream &cout, Person &p){
	cout<<"A= "<<p.A<<"B= "<<p.B;
	return cout;//这样就可以加endl了(运用链式编程思想)
}

使用时:cout<<p<<endl;

注:可以与友元进行配合从而输出自定义数据类型

三、递增运算符的重载(++)

重载递增运算符,实现自己的整形数据

  • 前置++

myinteger& operator(){
	num++;
	return *this;
}

返回引用方便进行链式编程

  • 后置++

myinteger operator(int)//int代表占位参数用于区分前置和后置
{
		//先记录当时结果
	myinteger temp = *this;
		//再进行++
	num++;
	return temp;
}

不能返回引用,因为局部对象temp在函数执行完就被释放了,要返回值

四、赋值运算符重载

编译器有自己提供的operator=,但是只是浅拷贝,在释放堆区数据时代码易发生崩溃
故利用深拷贝

  • 重载

Person& operator=(Person &p)
{
	if(m_Age!=NULL){
		delete m_Age;
		m_Age=NULL;
	}
	m_Age= new int(*p.m_Age);
	return *this;
}
五、关系运算符重载(==、!=)
  • 在类内重载(==)

bool operator==(Person &p){
	if (this->m_name == p.m_name && this->m_age == p.m_age){
		return true;
	}
	return false;
}
  • !=的重载

bool operator!=(Person &p){
	if (this->m_name == p.m_name && this->m_age == p.m_age){
		return false;
	}
	return true;
}
六、函数调用运算符重载
  • 在类内

void operator()(string test){
	cout<<test<<endl;
}
void test01(){
	Myprint myprint;
	mypeint("hello world");
}

由于重载后的与普通函数十分相似,故也称成为仿函数

特点:

  • 没有固定形式,非常灵活

匿名函数对象
通过函数名加小括号即MyAdd()(1,1)//匿名对象在当前行被执行完了就立即被释放

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值