C++类与对象常考知识

目录

α.友元

1.友元函数

2.友元类

β.静态成员(static)

γ.程序的内存模型

内存分区模型

1.程序运行前

2.程序运行后

练习巩固

δ.new / delete 操作

1.基本语法

2.初始化new数组的问题

3.new和delete操作自定义类型


α.友元

友元分为:友元函数友元类
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。



1.友元函数

友元函数 可以 直接访问 类的 私有 成员,它是 定义在类外部 普通函数 ,不属于任何类,但需要在类的内部声明,声明时需要加friend 关键字。
#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 修饰。

③ 友元函数可以在类定义的任何地方申明,可以不受类访问限定符的控制。

④ 一个函数可以是多个类的友元函数。

⑤ 友元函数的调用和普通函数的调用原理相同。

2.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  • 友元关系是单向的,不具有交换性:

        比如下文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 修饰的成员变量,称为静态成员变量。

用 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)

        堆用于程序运行时动态内存分配,堆是可以上增长的。

一般由程序员自主分配释放,若程序员不主动不释放,程序结束时可能由操作系统回收。

其分配方式类似于链表。

1.程序运行前

在程序编译后,生成了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;
}

输出:

C++类与对象常考知识_第1张图片

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

C++类与对象常考知识_第2张图片

2.程序运行后

 栈区:

​ 由编译器自动分配释放, 存放函数的参数值,局部变量等

​ 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

#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++类与对象常考知识_第3张图片

堆区:

​ 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

​ 在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;
}

C++类与对象常考知识_第4张图片

总结:

  1. 堆区数据由程序员管理开辟和释放
  2. 栈区​由编译器自动分配释放
  3. 堆区数据利用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) = ____;

C++类与对象常考知识_第5张图片

  答案:CCCAA  AAADAB

注意:

#include
using namespace std;
int main() 
{
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	
	cout<<&("abcd")<

C++类与对象常考知识_第6张图片

字符串常量与局部常量的区别 

δ.new / delete 操作

C++中利用new操作符在堆区开辟数据

​ 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

1.基本语法

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;

}
注意:申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用 new[] delete[]

2.初始化new数组的问题


 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

3.newdelete操作自定义类型

在申请自定义类型的空间时,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时如图:

C++类与对象常考知识_第7张图片

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来释放空间

γδεζηθ

你可能感兴趣的:(C++学习路程,c++,开发语言)