今天开始着重学习C++面向对象的方法和知识点,以便填补自己的知识漏洞
1.友元
1.1友元函数和友元类的简单实现
友元的主要体现又友元函数和友元类,要在类内定义友元,只需在前面加上friend即可。要注意的是,友元虽然是在类内定义,但是它不是类的成员函数,只是类的“朋友”,允许访问类的全部成员(包括私有属性),权限与成员函数相同。
注意:调用友元函数的时候不要使用圆点.操作符,使用->
//友元
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
//友元
class Student{
public:
// 初始化函数
Student(string name, int age){
this->stu_name = name;
this->stu_age = age;
}
// 声明友元类 class Teacher
friend class Teacher;
// 公共方法work
void work(){
cout << this->stu_name << " is Working.." << endl;
}
private:
string stu_name;
int stu_age;
};
class Teacher{
public:
// 初始化函数
Teacher(string name, int age){
this->tea_name = name;
this->tea_age = age;
}
// 定义公共方法老师教学生,需要使用到Stduent类中的私有属性
void teach_std(Student student){
cout << "Teacher " << this->tea_name << " is teaching " << student.stu_name << endl;
}
// 定义一个老师类的公共方法是打印学生的姓名和年龄
void reply_age(Student *p);
private:
string tea_name;
int tea_age;
};
//具体实现
void Teacher::reply_age(Student *p){
cout << "Student " << p->stu_name << " is " << p->stu_age << endl;
}
int main (){
// 创建实例
Student s("小明", 18);
Teacher t("dzzhyk", 27);
// 因为老师类是学生类的友元类,因此老师类也可以使用学生类中的私有属性
t.reply_age(&s);
t.teach_std(s);
// 获取不到学生类中的方法
// t.work()
return 0;
}
/*
结果:
Student 小明 is 18
Teacher dzzhykis teaching 小明
可以看到在声明了友元类之后,在老师类中就可以使用到学生类中的私有属性
注意只能获取到私有的属性
*/
1.2友元的作用
- 在外部频繁操作对象(即调用成员函数)而引起调用开销增加时,可以通过直接访问对象的成员(而不是调用成员函数)来使得性能明显提高。简单地说,友元可以简化函数定义,从而提高效率。
- 用在无法成员话的操作符重载中
2.运算符重载
通常,运算符的分量是基本的数据类型,而当运算分量为对象的时候,其运算符具有新的功能,为运算符定义新功能的过程就成为运算符重载。运算符重载可以改进可读性,使得代码更加简洁。
2.1运算符重载规则
- 重载的运算符至少要有一个参数属于“类”的类型
- 不能新建一个运算符,只能对现有的运算符(如:++、--、+、-、/、%、>>、<<、关系运算符、赋值运算符、下标运算符[ ]、逗号运算符、new delete 等)进行重载。
- 不能改变运算符接受的参数数目,一元运算符重载后还是一元运算符,二元运算符重载后还是二元运算符。
- 重载的运算符保持原有的运算优先级
- 有的运算符不能重载: :: 、* 、 ?: 、sizeof、typeof
- 可以通过成员函数重载运算符,也可以通过友元函数重载运算符
- 运算符=、()、[ ] 、-> 和类型转换运算符只能以成员函数重载;运算符>>和<<只能以友元函数重载
- C++运算符总是位于表达式中,运算结果是要使用的,因此运算符函数的返回值不可能是void函数
2.2运算符重载的实现
//运算符重载
#include <iostream>
using namespace std;
//首先定义复数类
class Complex{
public:
// 初始化函数
Complex(float real=0.0, float virt=0.0){
this->real = real;
this->virt = virt;
}
// + 运算符重载 1 - 复数加实数
Complex operator + (float num){
return Complex(this->real + num, this->virt);
}
// + 运算符重载 2 - 复数加复数
Complex operator + (Complex c2){
return Complex(this->real + c2.real, this->virt + c2.virt);
}
// 输出复数
void cout_complex(){
cout << this->real << "+" << this->virt << "i" << endl;
}
private:
float real;
float virt;
};
int main (){
// 实例化
Complex c1(2, 4); //2+4i
Complex c2(1, 7); //1+7i
Complex ans; //初始为:0+0i
// 先打印一下ans
ans.cout_complex();
ans = c1 + c2;
ans.cout_complex();
// 还原ans的值
ans = Complex(0.0, 0.0);
ans = c1 + 89;
ans.cout_complex();
// 还原ans的值
ans = Complex(0.0, 0.0);
// 设置出c3只有虚部 50 ,为 50i
Complex c3(0, 50);
ans = c1 + c3;
ans.cout_complex();
return 0;
}
/*
结果:
0+0i
3+11i
91+4i
2+54i
*/
3.构造函数
构造函数就是与类名同名的类成员函数,被声明为public公有函数,具有一般成员函数所有的特性,可以被重载。声明对象时,经常需要初始化变量或者全体成员变量,此时构造函数就在创建对象的时候被自动调用执行,用于为对象分配空间和初始化成员变量的值,并且进行其他可能需要进行的初始化操作。
关键词:用于初始化操作
3.1构造函数的使用(重载)
//构造函数和析构函数
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
using namespace std;
class Person{
public:
// 构造函数 - 即为初始化函数,和类名相同,不加任何修饰
Person(string name, int age){
this->name = name;
this->age = age;
cout << "Instance " << this->name << " is created" << endl;
}
// 普通类方法
void eat(){
cout << this->name << " is eating" << endl;
}
private:
string name;
int age;
};
int main (){
// 实例化
Person p("dzzhyk", 19);
p.eat();
return 0;
}
/*
结果:
Instance dzzhyk is created
dzzhyk is eating
*/
3.2拷贝构造函数
当将一个对象要拷贝给另一个对象的时候,需要调用的函数就叫做拷贝构造函数。拷贝构造函数实际上也是构造函数,具有一般构造函数的所有特性,其名字也与所属类名相同。拷贝构造函数中只有一个参数,即对某个同类对象的引用。拷贝构造函数是重载构造函数的一种重要形式。大致格式如下:
ClassName :: ClassName(ClassName &a){...}
3.3调用拷贝构造函数的三种情况
- 用类的一个已经初始化的对象去初始化该类的另一个新构造的对象,即一个对象需要通过另一个对象进行初始化时。
- 一个对象以值传递的方式调用形参是类对象的函数,进行形参和实参结合的时候。
- 函数返回值是类的对象,函数执行完返回调用者时。
3.4浅拷贝与深拷贝
C++默认的拷贝构造函数是浅拷贝,浅拷贝就是对象的数据成员之间的简单赋值。例如:设计了一个类而没有提供它的拷贝构造函数,当用该类的一个对象给另一个对象赋值的时候所执行的就是浅拷贝。
//浅拷贝
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Person{
public:
Person(string name, int age){
this->name = name;
this->age = age;
}
void eat(){
cout << "eat" << endl;
}
void show(){
cout << "name: " << this->name << " age: " << this->age << endl;
}
private:
string name;
int age;
};
int main (){
Person p1("dzzhyk", 19);
Person p2 = p1;
p2.show();
return 0;
}
浅拷贝只拷贝对象成员,而不拷贝相关资源,源对象与拷贝对象公用一个实体,仅仅是引用的变量不同,对其中任何一个对象的更改都会影响到其他的对象。
浅拷贝存在的问题:
- 浅拷贝只是对指针的拷贝,拷贝之后两个指针指向同一个内存空间,这样在对象块结束、程序进行析构的时候,同一份资源连续发生两次析构,即delete同一块内存两次,可能造成程序错误
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Person{
string name;
// 取得name的地址
string *p = &name;
public:
// 初始化函数
Person(string name){
this->name = name;
}
// 重写析构函数
~Person(){
cout << "析构中: " << this->name << endl;
delete p;
}
private:
};
int main (){
Person p1("dzzhyk");
// 这里使用浅拷贝
Person p2 = p1;
return 0;
}
可以看到只能删除一次
深拷贝:指当前拷贝对象中有对其他资源的引用(可以是指针或者引用)时,拷贝对象要另外开辟一块新的资源,而不是进行单纯赋值
深拷贝不仅对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。
深拷贝实例:
#include <iostream>
#include <cstring>
using namespace std;
class Person{
char *name;
public:
Person(char *);
Person(Person &);
~Person();
};
Person::Person(char *a){
cout << "创建实例..." << endl;
name = new char[strlen(a) + 1]; // 深拷贝
if(name!=0)
{
strcpy(name, a);
}
}
// 自定义拷贝构造函数
Person::Person(Person &p){
cout << "正在进行深拷贝..." << endl;
name = new char[strlen(p.name) + 1];
if(name!=0){
strcpy(name, p.name);
}
}
Person::~Person(){
cout << "正在析构: " << this->name << endl;
delete name;
}
int main(int argc, char const *argv[])
{
Person p1("dzzhyk");
Person p2 = p1;
return 0;
}
结果:可以看到此时的析构结果正常
4.析构函数
析构函数也是类的成员函数,析构函数的名字是在类名的前面加上~,没有参数也没有返回值,不能重载。
4.1调用析构函数的四种情况
- 对象生命周期结束而被销毁的时候,在函数体内的对象,当函数执行结束时,该对象所在类的析构函数会被自动调用。
- 使用new运算符动态创建的对象,在使用delete运算符释放它的时候,会调用析构函数
- delete指向对象的基类类型指针,而其基类虚构函数时虚函数的时候
- 对象i是o的成员,当o的析构函数被调用的时候,i的析构函数也被调用。
4.2析构函数的作用
析构函数的作用是在撤销对象占用的内存之前完成一些清理工作,使得这部分内存可以被程序分配给新对象使用。对象的生命期结束之后,程序就自动执行析构函数来完成这些工作。
4.3析构函数实例
// 析构函数
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
string name;
Person(name){
cout << "创建实例" << endl;
this->name = name;
}
~Person(){
cout << "析构函数" << endl;
}
};
int main(int argc, char const *argv[])
{
Person p1("dzzhyk");
return 0;
}