目录
- #include <iostream>
- using namespace std;
- class Date
- {
- friend ostream& operator<<(ostream& _cout, const Date& d);
- friend istream& operator>>(istream& _cin, Date& d);
- public:
- Date(){}
- Date(int year, int month, int day)
- : _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;
- cin >> d;
- cout << d << endl;
- return 0;
- }
注意事项:
① 友元函数可以访问类的 private 和 protected 成员,但并不代表能访问类的成员函数。
② 友元函数不能用 const 修饰。
③ 友元函数可以在类定义的任何地方申明,可以不受类访问限定符的控制。
④ 一个函数可以是多个类的友元函数。
⑤ 友元函数的调用和普通函数的调用原理相同。
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性:
比如下文Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递:
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
一定要前置类声明!!
- #include <iostream>
- using namespace std;
- class Date; // 前置声明
- class Time
- {
- friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
-
- public:
- Time() {}
- Time(int hour, int minute, int second)
- : _hour(hour)
- , _minute(minute)
- , _second(second)
- {}
-
- private:
- int _hour;
- int _minute;
- int _second;
- };
- class Date
- {
- public:
- Date(int year = 1900, int month = 1, int day = 1)
- : _year(year)
- , _month(month)
- , _day(day)
- {}
-
- void SetTimeOfDate(int hour, int minute, int second)
- {
- // 直接访问时间类私有的成员变量
- _t._hour = hour;
- _t._minute = minute;
- _t._second = second;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- Time _t;
- };
-
- int main() {
- return 0;
- }
这里 Date 是 Time 的友元,我们在日期类里就可以访问时间类的私有成员了。
但是时间类里不能访问日期类,因为这是 "单向好友" ,如果想在时间类里访问日期类,我们可以在日期类里声明:
- class Date {
- friend class Time;
- // ...
- }
这样,它们之间就是 "双向好友" 了 —— 互相成为对方的友元。
复习:所有对象共享同一份数据;编译阶段就分配内存;类内声明类外初始化; 静态成员函数只能访问静态成员变量;
声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称为静态成员变量。
用 static 修饰的成员函数,称为静态成员函数,静态的成员变量一定要在类外进行初始化。
- class A {
- public:
- A() { ++_scount; }
- A(const A& t) { ++_scount; }
- static int GetACount() { return _scount; }
- private:
- static int _scount;
- };
- int A::_scount = 0;
- void TestA()
- {
- cout << A::GetACount() << endl;
- A a1, a2;
- A a3(a1);
- cout << A::GetACount() << endl;
- }
① 静态成员为所有类对象所共享,不属于某个具体的实例。
② 静态成员变量必须在类外定义,定义时不添加 static 关键字。
③ 类静态成员即可用类名 :: 静态成员变量或者对象 . 来访问。
④ 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员,static函数唯一能够访问的就是static变量或者其他static函数。
⑤ 静态成员和类的普通成员一样,也有 public、protected、private 三种访问级别,也可以具有返回值。
注意:静态成员只会被初始化一次!
C++程序在执行时,将内存大方向划分为4个区域
代码段(code segment)
可执行的代码 / 只读常量。代码段存放类成员函数和全局函数的二进制代码。
一个程序起来之后,会把它的空间进行划分,而划分是为了更好地管理。
函数调用,函数里可能会有很多变量,函数调用建立栈帧,栈帧里存形参、局部变量等等
全局区(data segment)
静态存储区,数据段存放全局变量和静态数据,程序结束后由系统释放。
栈区(stack)
栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。
执行函数时,函数内部局部变量的存储单元都可以在栈上创建。
函数执行结束后这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,
拥有很高的效率,但是分配的内存容量是有限的。
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
堆区(heap)
堆用于程序运行时动态内存分配,堆是可以上增长的。
一般由程序员自主分配释放,若程序员不主动不释放,程序结束时可能由操作系统回收。
其分配方式类似于链表。
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此.
全局区还包含了常量区, 字符串常量和其他常量也存放在此.
该区域的数据在程序结束后由操作系统释放.
举个栗子:
- #include <iostream>
- using namespace std;
- //全局变量
- int g_a = 10;
- int g_b = 10;
-
- //全局常量
- const int c_g_a = 10;
- const int c_g_b = 10;
-
- int main() {
-
- //局部变量
- int a = 10;
- int b = 10;
-
- //打印地址
- cout << "局部变量a地址为: " << &a << endl;
- cout << "局部变量b地址为: " << &b << endl;
-
- cout << "全局变量g_a地址为: " << &g_a << endl;
- cout << "全局变量g_b地址为: " << &g_b << endl;
-
- //静态变量
- static int s_a = 10;
- static int s_b = 10;
-
- cout << "静态变量s_a地址为: " << &s_a << endl;
- cout << "静态变量s_b地址为: " << &s_b << endl;
-
- cout << "字符串常量地址为: " << &"hello world" << endl;
- cout << "字符串常量地址为: " << &"hello world1" << endl;
-
- cout << "全局常量c_g_a地址为: " << &c_g_a << endl;
- cout << "全局常量c_g_b地址为: " << &c_g_b << endl;
-
- const int c_l_a = 10;
- const int c_l_b = 10;
- cout << "局部常量c_l_a地址为: " << &c_l_a << endl;
- cout << "局部常量c_l_b地址为: " << &c_l_b << endl;
-
- system("pause");
-
- return 0;
- }
输出:

观察输出的地址,我们发现其中局部变量和局部常量地址为一坨,而其余地址为另一坨,得出如图结论;

栈区:
由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
- #include<bits/stdc++.h>
- using namespace std;
- int * func()
- {
- int a = 10;
- return &a;
- }
-
- int main() {
-
- int *p = func();
-
- cout << *p << endl;
- cout << *p << endl;
-
- return 0;
- }
-

堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
- #include<bits/stdc++.h>
- using namespace std;
- int* func()
- {
- int* a = new int(10);
- return a;
- }
-
- int main() {
-
- int *p = func();
-
- cout << *p << endl;
- cout << *p << endl;
-
-
- return 0;
- }
-

总结:
- 堆区数据由程序员管理开辟和释放
- 栈区由编译器自动分配释放
- 堆区数据利用new关键字进行开辟内存(或者用malloc / calloc / realloc )
- 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 在哪里?____
-
- char2在哪里?____ *char2在哪里?___
- pChar3在哪里?____ *pChar3在哪里?____
- ptr1在哪里?____ *ptr1在哪里?____
-
-
- 2. 填空题:
- sizeof(num1) = ____;
- sizeof(char2) = ____; strlen(char2) = ____;
- sizeof(pChar3) = ____; strlen(pChar3) = ____;
- sizeof(ptr1) = ____;

答案:CCCAA AAADAB
注意:
- #include<bits/stdc++.h>
- using namespace std;
- int main()
- {
- char char2[] = "abcd";
- const char* pChar3 = "abcd";
-
- cout<<&("abcd")<<endl;
- cout<<&(char2)<<endl;
- cout<<&(pChar3)<<endl;
- return 0;
- }
-

字符串常量与局部常量的区别
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
- void Test_CPP() {
- // 动态申请一个int类型的空间
- int* p1 = new int;
-
- // 动态申请一个int类型的空间并初始化为10
- int* p2 = new int(10);
-
- // 动态申请10个int类型的空间
- int* p3 = new int[10];
-
-
-
- // 单个对象,delete即可。
- delete p1;
- delete p2;
-
- // 多个对象,delete[] 。
- delete[] p3;
-
- }
C++98 不支持初始化 new 数组:
int* p = new int[5];//报错
C++11 允许大括号初始化,我们就可以用 { } 列表初始化:
- int* p1 = new int[5]{1,2} // 1 2 0 0 0
- int* p2 = new int[5]{1,2,3,4,5}; // 1 2 3 4 5
在申请自定义类型的空间时,new 会调用构造函数,
delete 会调用析构函数,而 malloc 与 free 不会。
new:在堆上申请空间 + 调用构造函数输出。
delete:先调用指针类型的析构函数 + 释放空间给堆上。
malloc 和 new 的对比
- #include <iostream>
- #include<stdlib.h>
-
- using namespace std;
-
- class A {
- public:
- A()
- : _a(0) {
- cout << "A():" << this << endl;
- }
- ~A() {
- cout << "~A():" << this << endl;
- }
-
- private:
- int _a;
- };
-
- int main(void)
- {
- // 动态申请单个A对象和5个A对象数组
- A* p1 = (A*)malloc(sizeof(A));
- A* p2 = (A*)malloc(sizeof(A) * 5);
-
- //A* p3 = new A; // 后面只需要跟类型就可以
- //A* p4 = new A[5];
- }
将new注释发现没有输出,使用new时如图:

free 与 delete 的对比
- #include <iostream>
- #include<stdlib.h>
-
- using namespace std;
-
- class A {
- public:
- A()
- : _a(0) {
- cout << "A():" << this << endl;
- }
- ~A() {
- cout << "~A():" << this << endl;
- }
-
- private:
- int _a;
- };
-
- int main(void)
- {
- A* p1 = (A*)malloc(sizeof(A));
- A* p2 = (A*)malloc(sizeof(A) * 5);
-
- A* p3 = new A;
- A* p4 = new A[5];
-
- free(p1);
- free(p2);
-
- delete p3;
- delete[] p4;
-
- // ...
- }
相对的,free 只是把 p1 p2 指向的空间释放掉。
而 delete 不仅会释 p1 p2 指向的空间,delete 还会调用对应的析构函数。
new的原理
1. 调用operator new函数申请空间
2. 在申请的空间上执行构造函数,完成对象的构造
delete的原理
1. 在空间上执行析构函数,完成对象中资源的清理工作
2. 调用operator delete函数释放对象的空间
new T[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
2. 在申请的空间上执行N次构造函数
delete[]的原理
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
γδεζηθ