6.28 c++

1.友元函数

一个类是不能访问另一个类的私成成员的。友元关系提供了不同类或对象的成员函数之间,类的成员函数与一般函数之间进行数据共享的机制。
通俗的说:友元关系就是一个类主动声明哪些类或函数是它的朋友,进而给它们提供对本类的访问特许
在一个类中,可以利用关键字friend将其它函数或类声明为友元。如果友元是一般函数或类的成员函数,称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都自动成为友元函数。

#include<iostream>
#include <cmath>
using namespace std;
class Point {
public:
   Point(float x, float y):x(x),y(y){}
   Point(Point& p){
       printf("copy constructor:%x\n", p);
       x = p.x;
       y = p.y;
   };
   void show(){
       printf("show:%x, x=%f, y=%f\n", this, this->x, this->y);
   }
   friend float dist(Point &p1, Point &p2);
private:
   float x, y;
};

float dist(Point &p1, Point &p2){
    float x = p1.x - p2.x;
    float y = p1.y - p2.y;
    return sqrt(x*x + y*y);
}

int main() {
    Point p1(1, 1), p2(2, 2);
    cout << "The distance is: " << dist(p1, p2) << endl;
    return 0;
}

输出:The distance is: 1.41421
友元关系不能传递,友元是单向的,友元关系不能被继承
不能将类的私有函数作为其它类的友元函数

2.运算符重载

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
运算符重载的本质是函数重载

返回类型 operator 运算符名称(形参列表)
{
    重载实体;
}

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Complex operator+(const Complex&);

例子:关于复数的加法运算

#include<iostream>
using namespace std;

class complex {
public:
	friend complex operator+(const complex &c1,const complex &c2);
	complex():m_real(0.0),m_imag(0.0){}
	complex(double real,double imag):m_real(real),m_imag(imag){}
	void display()const{
		cout<<m_real<<"+"<<m_imag<<"i"<<endl;
	}
private:
	double m_real;
	double m_imag;
};



complex operator+(const complex &c1,const complex &c2){
	complex tmp(c1.m_real+c2.m_real,c1.m_imag+c2.m_imag);
	return tmp;
}

int main(){
	complex c1(4.3,5.8);
	complex c2(2.4,3.7);
	complex c3;
	c3=c1+c2;
	c3.display();
	return 0;
}

我们重载了运算符“+",该运算符只对complex对象有效。当执行c3=c1
+c2语句时,编译器检测到“+”号两边都是complex对象就会转化为类似下面的函数调用

c3=operator=(c1,c2);

在这里插入图片描述
(2)重载不能改变运算符的优先级和结合性。
(3)重载不会改变运算符的用法,原本有几个操作数、操作数在左边还是在右边,这些都不会改变。例如,“~”右边只有一个操作数,“+”总是出现在两个操作数之间,重载后也必须如此。
(4)运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的
(5)运算符重载函数既可以作为类的成员函数,也可以作为全局函数。

3.继承

继承是类与类之间的关系,是一个很简单且很直观的概念,与现实世界中的继承类似,如儿子继承父亲的财产。

继承(Inheritance) 可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如,类B继承于类A,那么类B就拥有类A的成员变量和成员函数。被继承的类被称为父类或基类。继承的类被称为子类或派生类。
派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。

(1)当创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。

(2)当需要创建多个类,它们拥有很多相似的成员变量或成员函数时,也可以使用继承,可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也方便后续修改成员。
继承的一般语法:

class 派生类名:[继承方式] 基类名
{
	派生类新增加的成员
}

(1)派生类的构造函数

基类的成员函数可以被继承,可以通过派生类对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。

#include<iostream>
using namespace std;
//基类people
class people{
public:
	people(char* name,int age):m_name(name),m_age(age){}
protected:
	char*m_name;
	int m_age;
};
//派生类student
class student:public people{
public:
	//people(name.age)就是调用基类的构造函数
	student(char* name,int age,float score):people(name,age),m_score(score){}
	void display(){
		cout<<m_name<<"的年龄是"<<m_age<<"成绩是"<<m_score<<"。"<<endl;
	}
private:
	float m_score;
};

int main(){
	student stu("小明",16,92.5);
	stu.display();
	return 0;
}

小明的年龄是16成绩是92.5。

(2)派生类的析构函数

和构造函数类似析构函数也不能被继承。与构造函数不同的是,在派生类的析构函数中不用显式地调用析构函数,因为每一类只有一个析构函数。

#include<iostream>
using namespace std;

class A{
public:
	A(){
		cout<<"A 构造"<<endl;
	}
	~A(){
		cout<<"A 析构"<<endl;
	}
};

class B:public A{
public:
	B(){
		cout<<"B 构造"<<endl;
	}
	~B(){
		cout<<"B 析构"<<endl;
	}
};

class C:public B{
public:
	C(){
		cout<<"C 构造"<<endl;
	}
	~C(){
		cout<<"C 析构"<<endl;
	}
};

int main(){
	C test;
	return 0;
}

A 构造
B 构造
C 构造
C 析构
B 析构
A 析构

(3)继承时的名字遮蔽

如果派生类中的成员(包括成员变量及成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类的新增成员,而不是从基类继承来的成员。

(4)多继承

在前面的例子中,派生类都只有一个基类,被称为单继承(Single Inheritance)。 除此之外,C++也支持多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。

class D:public A,private B,private C
{
 //类D新增加的成员
}

多继承中的构造和析构

#include<iostream>
using namespace std;

//基类
class BaseA{
public:
	BaseA(int a,int b):m_a(a),m_b(b){
		cout<<"BaseA 构造函数被调用"<<endl;
	}
	~BaseA(){
		cout<<"BaseA 析构函数被调用 "<<endl;
	}
	void show(){
		cout<<"m_a="<<m_a<<endl;
		cout<<"m_b="<<m_b<<endl;
	}
protected:
	int m_a;
	int m_b;
};
//基类
class BaseB{
public:
	BaseB(int c,int d):m_c(c),m_d(d){
		cout<<"BaseB 构造函数被调用"<<endl;
	}
	~BaseB(){
		cout<<"BaseB 析构函数被调用 "<<endl;
	}
	void show(){
		cout<<"m_c="<<m_c<<endl;
		cout<<"m_d="<<m_d<<endl;
	}
protected:
	int m_c;
	int m_d;
};

//派生类
class Derived:public BaseA,public BaseB{
public:
	Derived(int a,int b,int c,int d,int e):BaseA(a,b),BaseB(c,d),m_e(e){
		cout<<"Derived 构造函数被调用"<<endl;
	}
	~Derived(){
		cout<<"Derived 析构函数被调用"<<endl;
	}
public:
	void display(){
		BaseA::show();
		BaseB::show();
		cout<<"m_e="<<m_e<<endl;
	}
private:
	int m_e;
};

int main(){
	Derived obj(1,2,3,4,5);
	obj.display();
	return 0;
}

BaseA 构造函数被调用
BaseB 构造函数被调用
Derived 构造函数被调用
m_a=1
m_b=2
m_c=3
m_d=4
m_e=5
Derived 析构函数被调用
BaseB 析构函数被调用
BaseA 析构函数被调用

4.virtual关键字

1)一种是基类希望其派生类进行覆盖(override)的函数。这种函数,基类通常将其定义为虚函数(加virtual)。当我们使用基类的指针或者引用调用虚函数时,该调用将被动态绑定。
(2)另外一种是基类希望派生类直接继承而不需要改变的函数。
基类通过在其成员函数的声明语句之前加关键字virtual使得该函数执行动态绑定。
任何构造函数之外的非静态函数都可以是虚函数。

为了解决多继承时的命名冲突和冗余数据问题,C++提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在这里插入图片描述
菱形继承:

#include<iostream>
using namespace std;

//间接基类
class A{
public:
	A(){
		cout<<"A 的构造函数被调用"<<endl;
	}
protected:
	int m_a;
};

class B:virtual public A{
public:
	B(){
		cout<<"B 的构造函数被调用"<<endl;
	}
protected:
	int m_b;
};

class C:virtual public A{
public:
	C(){
		cout<<"C 的构造函数被调用"<<endl;
	}
protected:
	int m_c;
};


class D:public B,public C{
public:
	D(){
		cout<<"D 的构造函数被调用"<<endl;
	}
public:
	void seta(int a){m_a=a;}
	void setb(int b){m_b=b;}
	void setc(int c){m_c=c;}
	void setd(int d){m_d=d;}
private:
	int m_d;
};

int main(){
	D d;
	return 0;
}

A 的构造函数被调用
B 的构造函数被调用
C 的构造函数被调用
D 的构造函数被调用

5.限制类的创建

(1)只在堆中创建

当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后(离开作用域,所占内存自动释放),然后会自动调用析构函数。

编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。因此,将析构函数设为私有,类对象就无法建立在栈上了。

#include<iostream>
using namespace std;

class Person
{
public:
    Person(){
        printf("Constructor -----------\n");
    }
    void test(){
        printf("addr=%x\n", this);
    }
    void destroy(){
        delete this;
    }

private:
    ~Person() {
        printf("Destructor -----------\n");
    }

private:
    int age;
    string name;
};

int main(){
    //Person p1;
    Person* pp = new Person();
    pp->test();
    pp->destroy();

    return 0;
}

Constructor -----------
addr=2516c20
Destructor -----------

(3)只在堆上创建

如果想让对象只能在栈上,那就是不能让别人使用到new这个操作符。可以在class中重载了私有的成员函数new,而且delete也需要一起重载下。如下面的例子:

class A
{
private:
    void* operator new(size_t t){}     // 注意函数的第一个参数和返回值都是固定的
    void operator delete(void* ptr){}  // 重载了new就需要重载delete
    
public:  
    A(){}
    ~A(){}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值