目录
α.友元
1.友元函数
2.友元类
β.静态成员(static)
γ.程序的内存模型
内存分区模型
1.程序运行前
2.程序运行后
练习巩固
δ.new / delete 操作
1.基本语法
2.初始化new数组的问题
3.new和delete操作自定义类型
#include
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
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
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
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
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
using namespace std;
int main()
{
char char2[] = "abcd";
const char* pChar3 = "abcd";
cout<<&("abcd")<
字符串常量与局部常量的区别
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
#include
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
#include
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来释放空间
γδεζηθ