c++友元与动态内存

友元

        友元是一种定义在类外部的普通函数或类,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员的函数,但是它可以访问类中的私有成员。
        类具有封装和信息隐藏的特性,只有类的成员函数才能访问类的私有成元,程序中其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据都定义为公有的,这又破坏了隐藏特性。在某些情况下,特别是对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查都需要时间开销,影响了程序的运行效率。

优点缺点

优点:提高程序的运行效率。
缺点:破坏了类的封装性和隐蔽性。

友元函数

        类中私有和保护的成员在类外不能被访问,友元函数是一种定义在类外部的普通函数,其特点是能够访问类中私有成员和保护成员,即类的访问权限的限制对其不起作用。友元函数需要在类体内进行说明,在前面加上关键字friend。一般格式为:

friend <type> FuncName(<args>); // 关键字  返回类型 函数名(函数参数)
// friend float Func(Student &a);

友元函数不是成员函数,用法与普通的函数完全一致,只不过他能访问类中所有数据。友元函数破坏了类的封装性和隐蔽性,使得非成员函数可以访问类的私有成员,一个类的友元可以自由地用该类中的所有成员。

友元函数说明

  • 友元函数不是类的成员函数。
  • 友元函数近似于普通的函数,它不能带有this指针,因此必须将对象名或对象的引用作为友元函数的参数,这样才能访问到对象的成员。
#include <iostream>
using namespace std;

class CTestFriend {
public:
    CTestFriend() {
        cout << "调用默认的构造函数CTestFriend::CTestFriend().\n";
    }
    CTestFriend(int a, int b) { // 带有参数的构造函数
        x = a;
        y = b;
        cout << "x=" << x << "," << "y=" << y << endl;
    }
    int mulxy() { // 普通成员函数
        return x * y;
    }
    friend int sumxy(CTestFriend &obj) { // 内部声明友元函数
        return obj.x + obj.y;
    }

    friend int reducexy(CTestFriend& obj); // 外部声明友元函数
private:
    int x, y;
};

int reducexy(CTestFriend& obj) { // 外部友元函数
    return obj.x - obj.y;
}
int main() {
    CTestFriend obja;
    CTestFriend objb(50, 100);
    cout << "输出乘积:" << objb.mulxy() << endl;
    cout << "输出相加和:" << sumxy(objb) << endl;
    cout << "输出相减差:" << reducexy(objb) << endl;
    return 0;
}

c++友元与动态内存

友元函数与一般函数不同点

  1. 友元函数必须在类的定义中说明,其函数体可以在类内定义,也可以在类外定义;
  2. 它可以访问该类中的所有成员(共有的,私有的,保护的),而一般函数只能访问类中的公有成员。
    友元函数不受类中访问权限关键字的限制,可以把它放在类的私有部分,放在类的共有部分或者放在类的保护部分,起作用是一样的,在类中友元函数指定访问权限是不起作用的。友元函数的作用域与一般函数的作用域相同,谨慎使用友元函数,通常使用友元函数来取对象中的数据成员,而不是修改对象中的数据成员值,则肯定是安全的。
#include <iostream>
using namespace std;

class CTestFriend {
public:
    CTestFriend() {
        cout << "调用默认的构造函数CTestFriend::CTestFriend().\n";
    }
    CTestFriend(int a, int b) {
        x = a;
        y = b;
    }
    int getx() {
        return x;
    }
    int gety() {
        return y;
    }
    int sum() {
        return x + y;
    }
    friend int sum(CTestFriend& obj);
    friend int sumxy(CTestFriend& obj);
private:
    int x, y;
};

int sum(CTestFriend& obj) {
    return obj.x + obj.y;
}
int sumxy(CTestFriend& obj) {
    return obj.getx() + obj.gety();
}

int main() {
    CTestFriend obj1(1, 2), obj2(100, 200), obj3(1000, 2000);
    cout << "\nobj1.sum:" << obj1.sum() << endl;
    cout << "\nobj2.sum:" << sum(obj2) << endl;
    cout << "\nobj3.sum:" << sumxy(obj3) << endl;
    return 0;
}

c++友元与动态内存

友元类

友元类除了函数以外,还可以是类,及一个类可作为另一个类的友元,当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数,都可以访问一个类中的隐藏信息(包括私有成员和保护成员)。
定义友元类的语句格式如下:friend class 类名;

#include <iostream>
using namespace std;

class CTestFClassA {
public:
    CTestFClassA(double a, double b) {
        x = a;
        y = b;
    }
    double getx() { return x; };
    double gety() { return y; };
    friend class CTestFClassB;  // 定义类CTestFClassB为CTestFClassA友元
private:
    double x, y;
};

class CTestFClassB {
public:
    CTestFClassB(int n = 1) {
        k = n;
    }
    void dispsum(CTestFClassA& obj) {
        cout << obj.x+obj.y+k << endl;
    } // 求CTestFClassA的某个数据成员到这边来操作
private:
    int k;
};


int main() {
    CTestFClassA objA1(100, 200), objA2(200,200);
    CTestFClassB objB1(20);
    objB1.dispsum(objA1);
    objB1.dispsum(objA2);
    return 0;
}

c++友元与动态内存

注意

  1. 友元关系不能被继承。
  2. 友元关系是单向的,不具有交换性,若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
  3. 有缘关系不具有传递性,若类B是类A的友元,类C是类B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。

动态内存new/delete

在定义变量或数组的同时,即在内存为其开辟了指定的固定空间。

int n,a[10];
char str[100];

一经定义,即为固定地址的空间,在内存不能被别的变量占用。在程序内部需要根据实际需要开辟空间。如何根据需要在程序的运行过程中动态分配存储内存空间?

int n;
cin >> n;
float score[n][5]; // error:数组的维数必须是常量

利用new运算符可以在程序中开辟内存空间

new 数据类型[单位数];
new int[4] // 在内存中开辟了4个int型的数据空间,即16个字节。

new int; 其中new相当于一个函数,在内存开辟完空间后,返回这个空间的首地址,这时候,这个地址必须用一个指针保存下来,保证不会丢失。

int *p;
p = new int;  // p存放首地址
*p = 6;

可以用*p对这个空间进行运算。

同样,利用new运算符也可以开辟连续的多个空间(数组)

int main() {
    int* p,n;
    cin >> n;
    p = new int[n];
    for (int i = 0; i < n; i++) {
        cin >> p[i];
    }

    return 0;
}

c++友元与动态内存
可以用p[i]的形式来引用新开辟的内存单元。

注意:
new开辟的内存单元没有名字,指向其首地址的指针是引用其的唯一途径,若指针变量重新赋值,则用new开辟的内存单元就在内存中丢失了,别的程序也不能占用这段单元,直到重新开机为止。

int *p,a[4];
p = new int[4]; // 该段内存由于失去了名字,再也无法引用
p = a;

new运算符分配的空间,不能在分配空间时进行初始化,同样,用new开辟的内存单元,如果程序不主动收回,那么这段空间就一直存在,直到重新开机为止。delete运算符就是用来将动态分配到的内存空间归还给系统,使用格式为:

 int* point;
 point = new int;
 ...
 delete point;

注意在上面代码中point指针不能重新赋值,只有用new开辟的空间才能用delete收回。delete也可以收回用new开辟的连续的空间。

int* point;
cin >> n;
point = new int[n];
... ...
delete []point;

当内存中没有足够的空间给予分配时,new运算符返回空指针NULL(0)

#include <iostream>
using namespace std;

class CNewDelete {
public:
    CNewDelete() {
        cout << "\n调用默认的构造函数CNewDelete::CNewDelete()." << endl;
        str = new char[10];
    }
    ~CNewDelete() {
        cout << "\n调用析构函数CNewDelete::~CNewDelete()." << endl;
        delete []str;
    }
private:
    char* str;
};
int main() {
    char buffer[100]; // 栈上的局部数组
    CNewDelete* pobj = new(buffer) CNewDelete; // 在 buffer 上构造对象
    pobj->~CNewDelete();  // 手动调用析构函数清理资源,避免内存泄漏

    char* buffer2 = new char[100];
    CNewDelete* pobj2 = new(buffer2) CNewDelete; // 在 buffer 上构造对象
    pobj2->~CNewDelete();  // 手动调用析构函数清理资源,必须主动调用析构函数

    delete[]buffer2; // 堆内需要主动释放操作

    return 0;
}

c++友元与动态内存