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(){}
};