1. 友元
1.1 友元函数
来看这段代码:
#include using namespace std; class Date { friend ostream& operator<<(ostream& _cout, const Date& d); friend istream& operator>>(istream& _cin, Date& d); public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; }; ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "-" << d._month << "-" << d._day; return _cout; } istream& operator>>(istream& _cin, Date& d) { _cin >> d._year; _cin >> d._month; _cin >> d._day; return _cin; } int main() { Date d(2023, 7, 2); cout << d << endl; return 0; }
输出:
2023-7-2
当出现这一种我们需要他按照这个顺序调用,以cout为例:
让cout 在左边,流插入的数在右边的时候,我们得让参数按照这个顺序排列,
而如果我们放到类内的话,就会出现隐藏的this指针在左边,这样就会导致:
流插入操作变成:d << cout 就感觉非常的怪,
如果我们又想在类外定义,有想在private的情况下调用成员函数,
我们就可以使用友元函数也就是上面的:
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
其实就是 friend + 函数的声明,这就是友元函数,
友元函数可以让友元函数调用类内的私有成员。
除了友元函数,还有友元类:
1.2 友元类
友元类也是同理,友元类可以调用这个类的所有成员,
用法就是 friend + class + 类名;这里就不演示了。
2. 内部类
来看这段代码:
#include using namespace std; class A { public: class B // B天生就是A的友元 { public: void foo(const A& a) { cout << a._h << endl; // 可以调用 } private: int _a; }; void Print(const B& b) { //cout << b._a << endl; // 不能调用 } private: int _h; }; int main() { cout << sizeof(A) << endl; return 0; }
输出:
4
是的,这段代码的输出的是4,
内部类看起来是内部,其实并不是在这个类的内部,
所以我们求A的大小就是4,一个int的大小,
如果说要理解的话,可以理解成内部类B是A的友元,
但是这里要注意A不是B的友元,所以B能用A的私有,A不能有B的私有。
3. 匿名对象
来看代码:
#include using namespace std; class A { public: A(int a = 0) :_a(a) { cout << "A(int a)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { A a(1); //有名对象 A(1); //匿名对象 return 0; }
我们可以看到,有名对象的名字是a ,而匿名对象确实没有名字,
那匿名对象有啥用呢?
来看代码:
#include using namespace std; class A { public: A(int a = 0) :_a(a) { cout << "A(int a)" << endl; } void Push(int n = 10) { //... } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { A a(1); //有名对象 a.Push(); A(1).Push(); //匿名对象 return 0; }
这里就体现出了匿名对象的价值,
匿名对象能够直接调用类成员函数,
这里要注意的是,有名对象的生命周期在当前函数的局部域,
而匿名对象的生命周期只有那一行,走到下一行匿名对象就销毁了。
另外,匿名对象具有常性。
4. 内存管理
我们的程序中需要存储这样一些数据:
局部数据,静态数据和全局数据,常量数据,动态申请的数据。
我们来看一下这道题目:
#include using namespace std; int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }
// 1.选择题:
// 选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
//
// globalVar在哪里? ____
// staticGlobalVar在哪里? ____
// staticVar在哪里? ____
// localVar在哪里? ____
// num1 在哪里? ____
第一个空,他是一个全局变量,所以存在于静态区,选C;
第二个空,他是一个静态变量,所以也在静态区,选 C;
第三个空,他也是静态变量,所以也在静态区,选C;
第四个空,他是一个局部变量,所以存在于栈区,选A;
第五个空,他是一个局部数组,所以也存在于栈区,选A。
这五道题并不难,那我们再来几道题试试:
#include using namespace std; int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }
// 1.选择题:
// 选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
//
// char2在哪里? ____
// *char2在哪里? ____
// pChar3在哪里? ____
// *pChar3在哪里?____
// ptr1在哪里? ____
// *ptr1在哪里? ____
还是同样的问题,
我们一个个来分析一下:
第一个空,他是一个char类型的局部数组,所以在栈区,选A;
第二个空,*char2是取得这个局部数组首元素,所以在栈区,选A;
第三个空,他是一个指向常量字符串的指针,所以在栈区,选A;
第四个空,*pChar3是取得这个常量字符串的首元素,所以在常量区,选D;
第五个空,他是一个指向一段动态开辟的空间地址的指针,所以在栈区,选A;
第六个空,*ptr1是找到那快动态开辟的空间的第一个元素,所以在堆区,选B。
5. C++的动态内存
C语言中的动态内存,C++也能使用,但是,
C++自己也有自己的动态内存开辟方法,来看代码:
/
这两段代码的意义是一样的,
我们可以看到C++的方法要比C语言更简洁,
如果是申请多个元素呢?
#include using namespace std; int main() { int* p1 = (int*)malloc(sizeof(int)); free(p1); int* p2 = new int; delete p2; return 0; }
#include using namespace std; int main() { int* p1 = (int*)malloc(sizeof(int) * 10); free(p1); int* p2 = new int[10]; delete[] p2; return 0; }
这个就是申请出一个10个int大小的数组,
C++还能支持初识化单个对象:
/
这个就是将p指向的空间初识化成10。
#include using namespace std; int main() { int* p = new int(10); delete p; return 0; }
他还支持这样初始化数组:
/
这样可以按照你的想法进行初始化。
这里又有一个问题,难道C++就是为了能方便一点,
就设计new出来吗?
#include using namespace std; int main() { int* p = new int[10]{1, 2, 3}; delete[] p; return 0; }
实际上不是的,new有他独特的优势:
对于内置类型,new和malloc 是一样的,
但是,对于自定义类型,malloc 只是单纯的开空间,
而 new 会调用构造函数进行初始化,
来看例子:
#include using namespace std; struct ListNode { int _val; struct ListNode* _next; ListNode(int val = 0) : _val(val) , _next(nullptr) {} }; //这个是C语言的开空间方法 struct ListNode* BuyListNode(int x) { struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode)); if (newnode == nullptr) { perror("malloc::fail"); return; } newnode->_next = NULL; newnode->_val = x; return newnode; } int main() { //C语言的用法 struct ListNode* n1 = BuyListNode(10); //C++的开空间方法 ListNode* n2 = new ListNode(10); return 0; }
C++的new就会自动调用构造函数。
当然,new不仅仅是如此,下篇文章我会继续讲解。
写在最后:
以上就是本篇文章的内容了,感谢你的阅读。
如果感到有所收获的话可以给博主点一个赞哦。
如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~