C Pulse Pulse 引用、函数提高、类和对象

1、引用基本使用

1.1引用基本使用

        a,b指向同一块空间,a、b的修改会影响彼此

int main()
{
	int a = 13;
	//创建别名b;
	int& b = a;
	b = 100;
	cout << "a=" <<a<<"\n" <<"b="<<b<< endl;
	int c = 20;
	b = c;//复制操作,而不是引用
	cout << "a=" << a << "\n" << "b=" << b << "\n" << "c=" <<c << endl;
	return 0;
}

1.2注:

  1. 引用必须初始化
  2. 引用初始化后不可改变==从一而终

1.3引用作函数参数

void swap(int &A, int& B)
{
	int tmp;
	tmp = B;
	A = B;
	B = tmp;
}
int main()
{
    int a=10;
    int b=20;
    swap(a,b);
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;
    return 0;
}

这里使用引用作为函数参数,当调用函数给函数传参时,A为a的别名,B为b的别名,形参和实参指向同一块空间,因此形参的改变也会影响实参

1.4引用作函数返回值

        1.不要返回局部变量的引用

当返回局部变量的引用时,在函数调用完成后,局部变量及其值都被销毁,引用也没有具体值

        2.函数返回引用可以作左值

int& fun()
{
	static int a = 10;//使用static修饰使其成为全局变量
	return a;
}
int main()
{
	int& ret = fun();
	cout << "ret=" << ret << endl;//10
	fun() = 100;
	cout << "ret=" << ret << endl;//100
	return 0;
}

这里的fun()函数返回值为引用,可以作为赋值操作符“=”的左值,相当于给返回的引用赋值

1.5引用的本质

引用的本质在C++内部实现时一个指针常量

int main()
{
	int a = 10;
	//自动转换为 int* const ref=&a;这也就说明了引用不可更改
	int& ref = a;
	ref = 20;//自动转换为*ref=20;
}

1.6常量引用

作用:用来修饰形参,防止误操作

void print(const int& ref)
{
	cout << "ref=" << ref << endl;
}
int main()
{
	//int& ref = 10; err,引用必须引用合法的内存空间
	const int& ref = 10;
	//加入const之后,编译器会优化代码,变为"int tmp=10;const int& ref=tmp;"
	//同时加入const之后便不可修改
	print(ref);
	return 0;
}

 当调用函数时用引用作为形参并且不希望改变实参,可以使用const修饰

2、函数提高

2.1函数的默认参数

int func(int a, int b, int c = 10)
{
	return a + b + c;
}
int func(int a, int b=10, int c );
int main()
{
	int a = 10, b = 20, c = 30;
	cout <<"func(a,c)=" << func(a, c) << endl;//50
	cout <<"func(a,b,c)=" << func(a, b, c) << endl;//60
}
  • 如果传入数据,就用自己的数据,如果默认参数没有传入数据,那么就是用默认值
  • 如果某个位置有了默认参数,则从这个位置往后,从左到右都必须有默认值
  • 函数的声明和函数的实现只能其中一个有默认参数,否则会造成重定义默认参数

2.2占位参数

C++中函数的形参列表中可以有占位参数,用来占位,在调用函数时必须填补该位置

同时,占位参数还可以拥有默认参数

语法:返回值类型 函数名(数据类型){}

void func(int a, int = 10)
{
	cout << "wanyeweiyuwenhaitang" << endl;
}
int main()
{
	int a = 10;
	func(a, 111);//随便使用一个相应数据类型的值进行占位
    func(a);//因为func的占位参数拥有默认参数,因此也可以不进行填补
	return 0;
}

2.3函数重载

作用:函数名可以相同,提高复用性

条件:

  • 同一个作用域下(例如全局作用域下)
  • 函数名称相同
  • 函数参数类型不同个数不同或者顺序不同

注意事项:

  • 函数的返回值不能作为函数重载的条件
  • 引用作为重载条件
void func(int& a)
{
	cout << "func(int& a)" << endl;
}
void func(const int& a)
{
	cout << "func(const int& a)" << endl;
}
int main()
{
	//两个函数的参数类型不同,能够进行函数重载
	int a = 10;
	func(a);
	//这里的a为变量,可读可写,而const修饰的引用为只读,
	//所以一般会走没有用const修饰的函数
	func(10);
	//实参为常量时,int& a=10的引用是不能通过的
	//const int& a=10则可以通过
	return 0;
}

  • 函数重载碰到默认参数
void func(int a)
{
	cout << "func(int& a)" << endl;
}
void func(int a,int b=10)
{
	cout << "func(int a,int b=10)" << endl;
}
int main()
{
	int a = 10;
//函数重载碰到默认参数,出现二意性,报错
//尽量避免在函数重载使用默认参数
	func(a);
}

3、类和对象

C++面向对象的三大特性为:封装、继承、多态;C++认为万事万物皆为对象,对象上有其属性行为

3.1封装

3.1.1封装的意义:
  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

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

class Person
{
	//访问权限:public 公共\protected保护\private私人
public:
	//属性
	string m_name;//名字
	int age;//年龄

	//行为
	int HeartRate()//最大心率
	{
		return 220 - age;
	}
	void setName(string name)//设置名字
	{
		m_name = name;
	}
};
int main()
{
	//通过Person类创建p1对象
	Person p1;
	p1.age = 18;//给p1年龄赋值
	p1.setName("only");
	cout <<"p1最大心率为:" << p1.HeartRate() << endl;
	cout << "p1姓名为:" << p1.m_name << endl;
}
3.1.2封装的访问权限

类在设计时,可以把属性和行为放在不同的控制权限下,控制访问权限有三种:

  • public:        公共权限     类  内外均可访问
  • protected:保护权限     类内可以访问,类外不可访问
  • private:       私有权限     类内可以访问,类外不可访问
3.1.3 struct 和class的区别

在C++中struct与class的唯一区别是它们的默认权限不同

struct 的默认权限 为public

class 的默认权限  为private

3.1.4成员属性设置为私有

优点:

  • 将成员属性设置为私有,能够自己通过行为来控制成员的读写权限
  • 对于写权限,能够通过行为来检测数据的有效性,而不是在类外直接给属性赋值

3.2对象的初始化和清理

3.2.1构造函数和析构函数

构造函数:主要作用在于创建对象时为对象的成员进行赋值,由编译器自动调用,无须手动调用

1.语法:类名 ( ){ 函数体 };构造函数没有返回值也不写void,函数名称与类名相同

2.构造函数可以有参数,因此可以发生重载

3.程序在创建对象时会自动调用构造函数,无须手动调用,且只会调用一次

析构函数:主要用于对象在销毁前系统自动调用,执行一些清理工作

1.语法:~类名(){函数体};造函数没有返回值也不写void,函数名称与类名相同,在名称前加上符号~

2.析构函数不能有参数,因此不能重载

3.程序在对象销毁前自动调用析构,无须手动调用,且只会调用一次

3.2.2构造函数的分类和调用

分类:

1.按照有无参数: 无参构造(默认构造)  和      有参构造

2.按照类型分类: 普通构造                      和        拷贝构造

class Person
{
public:
	int age;
	Person()//无参构造/默认构造/普通构造
	{}
	Person(int a)//有参构造/普通构造
	{
		age = a;
	}
	Person(const Person& p)//有参构造/拷贝构造,注意拷贝构造参数形式
	{
		age = p.age;
	}
};

调用:

1.括号法,常用

int main()
{
	Person p1;//无参函数,不能写成Person p1();编译器会认为这是函数声明
	Person p2(9);//有参函数 Person(int a)
	Person p3(p2);//构造函数调用
}

2.显示法

Person p4 = Person(10);//有参构造函数
Person p5 = Person(p4);//拷贝构造函数
//Person(10)单独写就是匿名对象,当前行结束之后,马上析构
//不要利用拷贝构造函数初始化匿名对象
//Preson (p3);编译器会认为Person(p3)==Person p3

3.隐式转换法

Person p6 = 10;//有参构造函数 Person p6=Person(10)
Person p7 = p6;//拷贝构造函数 Person p7=Person(p6)
3.2.3拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况:

  • 使用一个已经创建完毕的对象来初始化一个新对象
class Person
{
public:
	int age;
	Person()//无参构造
	{}
	Person(int a)//有参构造
	{
		age = a;
	}
	Person(const Person& p)//拷贝构造,注意拷贝构造参数形式
	{
		age = p.age;
	}
};
int main()
{
	Person p1(10);
	Person p2(p1);//用一个已经创建完毕的对象来初始化一个新对象
}
  • 值传递的方式给函数参数传值
void dowork(Person p1){}
void test()
{
	Person p;//调用默认构造函数
	dowork(p);//进行值传递,形参是实参的临时拷贝,
//相当于Person p1=p==>> Person p1=Person(p);
}
  • 以值方式返回局部对象
Person dowork()
{
	Person p1;
	return p1;//这里p1以值的形式返回,而不是地址的形式
}
void test2()
{
	Person p2 = dowork();
//p2在接受dowork的返回值时相当于==>>Person p2= p1;
}

3.2.4构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造函数

如果用户定义拷贝构造函数,C++不会再提供其他构造函数
 

3.2.5浅拷贝与深拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

class Person
{
public:
	int m_age;
	int* m_height;
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age, int height)
	{
		m_age = age;
		m_height = new int(height);
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person& p) 
	{
		m_age = p.m_age;
		m_height = new int(*p.m_height);
	}
	~Person()
	{
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "Person的析构函数调用" << endl;
	}
};
int main()
{
	Person p1(20, 165);
	cout << "p1的年龄为:" << p1.m_age << "  身高为  " << *p1.m_height << endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_age << "  身高为  " << *p2.m_height << endl;
	//局部变量都存储在 栈区,栈区先进后出,因此在销毁p2之前会先调用析构函数
	//析构函数会释放在 堆区 开辟的空间,而之后   p1的销毁也会 调用析构函数
	//创建对象p2时 使用的是系统提供的拷贝构造函数,会将成员的值 逐个字节 进行拷贝
	//从而使p1 p2中的height指向同一块空间,最终在p1销毁前会多次释放该空间
	//解决办法是 自己写一个拷贝构造函数
	return 0;
}

3.2.6初始化列表

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

class Person
{
public:
	int m_A;
	int m_B;
	int m_C;
	Person() :m_A(10), m_B(20), m_C(30) {}
	Person(int  a, int b, int c) :m_A(a), m_B(b), m_C(c) {
		cout << "有参构造函数的调用"<<endl;
	}
};
int main()
{
	Person p;
	//Person p(10,20,30);没有有参构造函数时,可在创建变量时直接进行初始化
	cout << p.m_A <<" " << p.m_B <<" " << p.m_C << endl;
	Person p2(30, 20, 10);//这里则是创建对象时,调用的有参构造函数
	cout << p2.m_A <<" "<< p2.m_B <<" "<< p2.m_C << endl;
}

3.2.7类对象作为类成员
class Phone
{
public:
	string m_name;
	Phone(string name) :m_name(name)
	{
		cout << "Phone的构造函数调用" << endl;
	}
	~Phone() { cout << "Phone的析构函数调用" << endl; }
};
class Person
{
public:
	string m_name;
	Phone m_phone;
	Person(string name, string pname):m_name(name),m_phone(pname)
//这里的m_phone(pname)相当于 Phone m_phone=pname
//相当于构造函数调用的隐式转换法==>>Phone m_phone(pname);
	{
		cout << "Person的构造函数调用" << endl;
	}
	~Person() { cout << "Person的析构函数调用" << endl; }
};
int main()
{
	Person p("张三", "小米");
}

通过执行结果可以看出,类对象作为类成员时:

构造时先构造 类  对象,再构造类  自身

析构时先析构 类  自身,   再析构类  对象

3.2.8静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员静态成员分为:
静态成员变量:

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

静态成员函数:

  • 所有对象享同一个函数
  • 静态成员函数只能访问静态成员变量
class Person
{
public:
	static int m_A;//类内声明
	int m_B;
	static void func()
	{
		m_A = 666;
		//m_B=777;不能对非静态成员变量进行访问,无法区分是哪个对象的m_B;
	}
};
int Person::m_A = 100;//类外初始化
int main()
{
	Person p1;
	cout << "p1= " << p1.m_A << endl;
	Person p2;
	p2.m_A = 200;
	Person::m_A = 200;
	cout << "修改后 p1= " << p1.m_A << endl;
//两种访问静态成员的方式:
//1.通过对象访问 2.通过类名访问
//可以将静态成员看作不属于具体某个成员,而是属于这个 类
	p2.func();
	Person::func();
	cout << "修改后 p1= " <<p1.m_A << endl;
}

3.3C++对象模型和this指针

3.3.1成员变量和成员对象分开存储

在C++中,类内的成员变量和成员函数分开存储,只有非静态的成员变量才属于类的对象上

C++中空对象占一个字节

class Person
{

};
int main()
{
	Person p;
//C++编译器会给每个空对象也分配 一个字节 空间,是为了区分空对象  占内存的位置
	cout << "空类的大小为:" << sizeof(p) << endl;
}
3.3.2this指针概念

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这—块代码是如何区分那个对象调用自己的呢?


C++通过提供特殊的对象指针,this指针,解决上述问题。this指针是隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用即可

this指针指向被调用的成员函数所属的对象

this指针的用途:

  • 当形参和成员变是同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this 则返回值需要使用 类型&
    class Person
    {
    public:
    	int age;
    	Person(int age)
    	{
        this->age = age;//通过this指针区分形参和成员变量
        }
    	Person& func(Person p)
    	{age += p.age;
    	return *this;}
    };
    int main()
    {
    	Person p1(20);
    //此时,this指针指向 被调用的成员函数 所属的对象,也就是p1
    //通过this指针来区分形参和成员变量;
    	Person p2(30);
    	p2.func(p1).func(p1).func(p1);
    //func函数的返回值如果是 Person类型,则结果为50;
    //这是因为Person返回类型 以值的方式返回,会重新创建一个Person p2'对象
    	cout << "p2的年龄为:" << p2.age << endl;
    }

3.3.3空指针访问成员函数

C++中空指针也可以调用成员函数,但要注意有没有使用到this指针,如果有用到this指针,需要加以判断

class Person
{
public:
	int age;
	void showClassName()
	{
		cout << "this class name is Person" << endl;
	}
	void showAge()
	{
		cout << "this age is " <<age<< endl;
	}
};
int main()
{
	Person* p = NULL;
	p->showAge();
	p->showClassName();
}

        能够看到程序出错,这是由于程序会在属性之前默认加上 this->,即:

cout << "this age is " <<this->age<< endl;

但 p指向NULL,并没有创建确切的值,因此this也为NULL;为了代码不崩溃,可加以判断:

  • void showAge()
    {
    	if (this == NULL)
    	    return;
    	cout << "this age is " <<this->age<< endl;
    }
3.3.4const修饰成员函数

常函数:

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


常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
  • 常对象依然可以修改加关键字mutale的成员属性
    class Person
    {
    public:
    	int age;
    	mutable int m_A;//即使在常函数、常对象中,也可以修改这个值
    	void func()const
    	{
    		//age = 100;
    		m_A = 100;
    	}
    //this指针的本质是 指针常量 指针的指向是不可以修改的;相当于Person* const this;
    //当函数后加const后,函数内的this指针 相当于const Person* const this;
    //让指针的值也不可以修改
    	void show(){}	
    };
    int main()
    {
    	const Person p;
    	//p.age = 100;不可修改
    	p.m_A = 100;//mutable修饰的成员属性可以修改
    	p.func();
    	//p.show;常对象只能调用常函数
    	//因为在非常函数中,有可能修改普通成员属性
    }

3.4友元

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

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
3.4.1全局函数作友元
class Person
{
	friend void godGay(Person* p);
public:
	int height;
private:
	int age;
	string password;	
};
void godGay(Person* p)
{
//通过全局函数访问私有属性需要在类中声明该函数,并在声明前加上关键字friend
	cout << p->age << endl;
	cout << p->password << endl;
}
3.4.2类做友元
class Building;//提前声明Building, 方便在GoodGay中使用
class GoodGay
{
public:
	void visit();
	Building* building;
	GoodGay();
};

class Building
{
friend class GoodGay;//类作为友元
public:
	string sittingroom;
	Building();
private:
	string bedroom;
};
//类外实现函数
Building::Building()
{
	sittingroom = "客厅";
	bedroom = "卧室";
}
GoodGay::GoodGay()
{
	//创建建筑物对象
	building = new Building;
	//在new Building 时也会调用Building的构造函数
}
void GoodGay::visit()
{
	cout << "GoodGay正在访问:" << building->sittingroom << endl;
	cout << "GoodGay正在访问:" << building->bedroom << endl;
}
int main()
{
	GoodGay g1;
	g1.visit();
}
3.4.3成员函数作友元
class Building
{
friend void GoodGay::visit();
//GoodGay中的成员函数作为Building的友元
//注意需要在visit()前加上作用域 GoodGay::
public:
	string sittingroom;
	Building();
private:
	string bedroom;
};

3.5运算符重载

概念:对已有的运算符就行重定义,赋予其另一种功能,以适应不同的数据类型

3.5.1加号运算符重载
class Person
{
public:
	int m_A, m_B;
	//Person operator+(Person& p)//使用成员函数实现
	//{
	//	Person tmp;
	//	tmp.m_A = this->m_A + p.m_A;
	//	tmp.m_B = this->m_B + p.m_B;
	//	return tmp;
	//}
	Person()
	{
		m_A = 10;
		m_B = 10;
	}
};
//使用全局函数实现
Person operator+(Person& p1, Person& p2)
{
	Person tmp;
	tmp.m_A = p1.m_A + p2.m_A;
	tmp.m_B = p1.m_B + p2.m_B;
	return tmp;
}
int main()
{
	Person p1,p2,p3;
	//1.成员函数重载本质
	//p3 = p1.operator+(p2);
	//2.全局函数重载本质
	//p3=operator+(p1,p2);
	p3 = p2 + p1;
	cout << "p3.m_A=" << p3.m_A << endl;
	cout << "p3.m_B=" << p3.m_B<< endl;
}

总结

  • 对于内置的数据类型的表达式的的运算符是不可能改变的
  • 不要滥用运算符重载,比如:使用operator+而在函数实现中实现的是减法

3.5.2左移运算符重载
class Person
{
public:
	int m_A;
	int m_B;
	Person(int a, int b)
	{
		m_A = a;
		m_B = b;
	}
};
//只能使用全局函数实现
void operator<<(ostream& out, Person p)
//cout是标准的输出流对象,全局只有一个,因此要使用引用
//本质 operator<<(out,p)  简化:out<<p
{
	cout << " m_A=" << p.m_A << " m_B=" << p.m_B;
}
int main()
{
	Person p1(13, 14);
	cout << p1;
}

虽然上述代码能够进行打印,但是只能打印一次,不能再在其后打印,这是因为函数的返回值为void ,如果想继续追加,则须返回 标准输出流

ostream& operator<<(ostream& out, Person p)
//cout是标准的输出流对象,全局只有一个,因此要使用引用
//本质 operator<<(out,p)  简化:out<<p
{
	cout << " m_A=" << p.m_A<< " m_B=" << p.m_B;
	return out;
}
int main()
{
	Person p1(13, 14);
	Person p2(5, 20);
	cout << p1 << p2 << endl;
}
3.5.3递增运算符重载
class MyInteger
{
public:
	int myint;
	MyInteger()
	{
		myint = 0;
	}	
	//前置++重载
	MyInteger& operator++()//返回引用是为了一直对一个数据进行递增操作
	{
		myint++;
		return *this;
	}
	//后置++重载
	MyInteger operator++(int)//使用int作为占位参数防止重定义,且只能使用int
	{
		MyInteger tmp=*this;
		myint++;
		return tmp;
	}
};
ostream& operator<<(ostream& out, MyInteger p)
{
	cout << "myint="<<p.myint;
	return out;
}
int main()
{
	MyInteger p1;
	cout << ++p1 << endl;
	cout << p1++ << endl;
	cout << p1 << endl;
}
ostream& operator<<(ostream& out, MyInteger p)
//注意这里p如果使用引用的方式,则后置++的参数传递会出现问题
3.5.4赋值运算符重载

C++编译器至少给一个类添加4个函数

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝
  • 赋值运算符operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
 

class Person
{
public:
	int* m_age;
	Person(int age)
	{
		m_age = new int(age);
	}
	~Person()
	{
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	}
	Person& operator=(Person& p)
	{
		//编译器提供浅拷贝
		//m_age = p.m_age;
		//先判断是否有属性在堆区,如果有,先释放
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
		//深拷贝
		m_age = new int(*p.m_age);
		//为了实现连续赋值,应返回本身;
		return *this;
	}
};

int main()
{
	Person p1(18);
	Person p2(28);
	Person p3(38);
	p3=p2 = p1;
}
3.5.5关系运算符重载
class Person
{
public:
	string m_Name;
	int m_Age;
	Person(string name, int age)
	{
		m_Name = name;
		m_Age = age;
	}
	bool operator==(Person& p)
	{
		if (m_Name == p.m_Name && m_Age == p.m_Age)
			return true;
		else
			return false;
	}
	bool operator!=(Person& p)
	{
		if (m_Name == p.m_Name && m_Age == p.m_Age)
			return false;
		else
			return true;
	}
};
int main()
{
	Person p1("谢尔比", 20);
	Person p2("谢尔比", 20);
	if (p1 == p2)
		cout << "p1和p2是相等的" << endl;
	else
		cout << "p1和p2是不相等的" << endl;
	if (p1 != p2)
		cout << "p1和p2是不等的" << endl;
	else
		cout << "p1和p2是相等的" << endl;
}
3.5.6函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值