系列文章目录

第零篇:从C到C++入门:C++有而C语言没有的基础知识总结-CSDN博客

第一篇:【C++闯关笔记】封装①:类与对象-CSDN博客

第二篇:【C++闯关笔记】封装②:友元与模板-CSDN博客

第三篇:【C++闯关笔记】STL:string的学习和使用(万字精讲)-CSDN博客


目录

系列文章目录

一、什么是友元

1.友元函数

2.友元类

内部类

二、C++程序的内存布局

三、静态成员与const成员

1.静态成员

2.const成员

3.静态成员和count成员的区别与联系

四、模板

1.函数模板

函数模板的实例化

2.类模板

总结



一、什么是友元

友元提供了一种突破封装的方式,在特定情况下提供了便利。但友元破坏了封装,并友元会增加耦合度,所以友元不宜多用

友元大致可分为友元函数与友元类。

1.友元函数

概念

友元函数是定义在类外部的普通函数,可以直接访问类的私有成员。友元函数不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

示例

class Date
{friend void test_display(const Date& d);public:private:int _year;int _month;int _day;
};void test_display(const Date& d)
{cout << d._year << d._month << d._day << endl;
}

友元函数细节

1.友元函数可访问类的私有和保护成员,但友元函数不是类的成员函数;

2.友元函数不能用const修饰;

3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制 一个函数可以是多个类的友元函数;

4.友元函数的调用与普通函数的调用原理相同;

什么情况下会用到友元函数呢?

假如我们有一个Date日期类,我们想通过cout直接输出Date类的对象,如:

class Date
{
public:Date(int year = 2025, int month = 8, int day = 20):_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};
Date d1(2025,2,1);
cout<<d1<<endl;

为了达到目标,我们可以通过重载“<<”操作符实现,问题是:是以成员函数的方式实现呢,还是以全局普通函数加友元的方式实现?

先本能的试试成员函数,毕竟重载目标就是专用于输出Date类,

友元函数实现:

	ostream& operator<<(ostream& out){out << _year << _month << _day;return out;}

解释:

①我们平常使用的cout(std::cout)是std::ostream类的一个预定义好的全局对象,该种被关联到标准输出(通常是终端屏幕)。形象的理解:编译器在一开始就为我们创建好了一个对象cout供我们使用输出(cin同理)。

②“<<”运算符,本质上是C语言中的移位运算符,在“iostream库”中重载了“<<”运算符并作为实现为 ostream 类的成员函数,功能就是输出(“>>“同理)。

③所以,cout是ostream类的对象,"<<"是ostream的成员函数 。故当我们想要重载"<<"时返回值应该ostram类型,形参也为ostream类型(用于接受调用”cout<<“时传入的cout)。

OK,到这里重载没有问题,可仔细一想这成员函数该怎么调用呢?

Date d(1000,11,2);
d.operator<<(cout);

这样的调用方式明显不符合日常使用,故”operator<<“和”operator>>“的重载,应该用普通全局函数加友元实现。

全局函数实现加友元实现:

class Date
{friend ostream& operator<<(ostream& out, const Date& d);
public:Date(int year = 2025, int month = 8, int day = 20):_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out,const Date& d)
{out << d._year << d._month << d._day;return out;
}

当想要输出Date类对象时,直接可以使用"<<"运算符输出,如下。

Date d(2000,1,3);
cout<<d;

通过上面有关哦ostraem的解释,我们现在可以明白:实际上cout<<d,在编译器执行时应该是cout.<<(this,d)或者<<(cout,d),cin同理。

2.友元类

概念

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

示例

通过定义一个Time类,将Date设置为Time的友元类,可以实现Date的成员函数访问Time的私有成员:

class Time
{friend class Date;
public:Time(int h=1, int m=1, int s=1) :_hour(h), _min(m), _sec(s){ }
private:int _hour;int _min;int _sec;
};class Date
{public:Date(int year = 2025, int month = 8, int day = 20,const Time& t = Time()) :_year(year), _month(month), _day(day), _t(t){}void Display(){cout << "year:" << _year << " month:" << _month << " day:" << _day;cout << " hour:" << _t._hour << "min: " << _t._min << " sec:" << _t._sec << endl;}private:int _year;int _month;int _day;Time _t;
};

友元类的特性

1.友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

2.友元关系不能传递。如果C是B的友元, B是A的友元,不能得出C是A的友元。

3.友元关系不能继承。

内部类

概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。

内部类特性

1.内部类是一个独立的类, 它不属于外部类。更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

2.内部类就是外部类的友元类。内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。

3. 内部类可以任意定义在外部类的public、protected、private。

4. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

5. sizeof(外部类)=外部类,和内部类没有任何关系。

示例

class Date
{public:class Time{friend class Date;public:Time(int h = 1, int m = 1, int s = 1) :_hour(h), _min(m), _sec(s){}void Display(const Date& d){cout << "year:" << d._year << " month:" << d._month << " day:" << d._day;cout << " hour:" << _hour << "min: " << _min << " sec:" << _sec << endl;}private:int _hour;int _min;int _sec;};Date(int year = 2025, int month = 8, int day = 20) :_year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};


二、C++程序的内存布局

若想真正探究清楚C++程序的具体运行逻辑,只写代码肯定是流于表面的,需要深入探究程序的运行机制,这是成为优秀程序员的必经之路。

C++程序的内存布局(从低地址到高地址)大致可分为:①代码段;②数据段(又可细分为已初始化数据和未初始化数据);③堆区;④共享区(堆或栈可拓展至此);⑤栈区;⑥内核空间(操作系统专属)。

图示如下

下面详细介绍每个区域的内容与特性

  1. 代码段 

    • 存放内容:编译后的机器指令(你的函数代码)、字符串常量(如 "Hello")。

    • 特性:通常是只读的与共享的,多个相同进程可以共享同一份代码段以节省内存。

  2. 已初始化数据段 

    • 存放内容已初始化全局变量静态变量(包括静态局部变量、静态成员变量)。

    • 特性:在程序开始前就分配好空间并初始化,生命周期贯穿整个程序。

  3. 未初始化数据段 

    • 存放内容未初始化全局变量静态变量

    • 特性:在程序开始前由操作系统将其内容全部初始化为零。这也是为什么未初始化的全局变量默认是0的原因。

  4. 堆区Heap

    • 存放内容:由 mallocnewnew[] 等动态分配的内存。

    • 特性:由程序员手动管理其生命周期(freedelete)。向上增长(向共享区)。分配速度较慢,可能会产生内存碎片。

  5. 栈区Stack

    • 存放内容局部变量函数参数返回值等。

    • 特性:由编译器自动管理。每个函数调用都会在栈上创建一个新的栈帧向下增长(向共享区)。分配和释放速度极快。

  6. 内核空间

    • 存放内容:操作系统内核的代码和数据。

    • 特性:用户程序无法直接访问。


三、静态成员与const成员

1.静态成员

静态成员概念

声明为static的类成员称为类的静态成员:

用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。

静态成员变量一定要在类外进行初始化。

示例

class Date
{public:private:int _year;int _month;int _day;static int count;
};int Date::count = 0;

静态成员特性

1. 静态成员为所有类对象所共享,存放在静态区(数据段);

2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明;

3. 类静态成员即可用:类名::静态成员,或者 对象.静态成员 来访问;

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员;

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

通过对静态成员特性的分析,我们可以得到两个有价值的结论:

1. 静态成员函数可以调用非静态成员函数吗?不能。

静态成员函数没有this指针,不能调用非静态成员函数;

2. 非静态成员函数可以调用类的静态成员函数吗?可以。

静态成员函数的调用不需要对象实例。

2.const成员

const成员概念

声明为static的类成员称为类的常态(const)成员:

const修饰的成员变量的值,在实例化出的对象中不可更改;

const修饰类成员函数,实际修饰该成员函数的this指针,表明在该成员函数中不能对类的任何成员进行修改。

示例

class Date
{public:Date(int year = 2025, int month = 8, int day = 20 ,int cnt=0) :_year(year), _month(month), _day(day),_cnt(cnt){}Date(const Date& d):_cnt(d._cnt){cout << "const Date& d" << endl;this->_year = d._year;this->_month = d._month;this->_day = d._day;}private:int _year;int _month;int _day;const int _cnt;
};

值得注意的是,具有const成员变量的类在设计拷贝构造函数时,必须用初始化列表显式地用源对象的值来初始化新对象的const成员。

const成员特性

1.const对象不能调用非const成员函数;

2. 非const对象可以调用const成员函数;

3. const成员函数内不可以调用其它的非const成员函数;

4. 非const成员函数内可以调用其它的const成员函数。

3.静态成员和count成员的区别与联系

首先来看它们的区别:

特性static 成员 (静态成员)const 成员 (常量成员)
核心语义“属于类”而非对象“不可修改”
内存中的副本数唯一 一份,所有类对象共享每个对象都拥有自己的一份
生命周期程序开始时创建,结束时销毁,生命周期随程序随着对象的创建而创建,销毁而销毁,生命周期随对象
内存位置数据段取决于对象本身(对象在栈,它就在栈;对象在堆,它就在堆)
初始化方式必须在类外单独定义和初始化(除了整型静态常量)必须在类的构造函数的初始化列表中初始化
访问方式既可以通过对象访问(obj.staticVar),也可以通过类名访问(Class::staticVar)只能通过对象实例访问(obj.constVar)

静态成员和count成员的联系——组合为“静态常量成员”

连着的组合创建了一个所有对象共享的、不可修改的常量。它就像是属于这个类的“全局常量”。

static const组合在C++中非常常见,主要用于:

①定义类相关的常量:比如数学类中的π值。

②定义数组大小:这是非常经典的用法。


四、模板

在编程中我们可能会遇到这种场景:两个不同类型的变量,需要经过同样的处理操作。如果根据数据类型定义两个重载函数感觉有些麻烦,能否有种“方便的模具函数”,使得需要经过同样处理操作的变量都可以调用?

C++为实现泛型编程,引入了模板概念,即告诉编译器一个模具,让编译器根据不同的类型利用该模子来生成代码。

1.函数模板

函数模板概念

函数模板代表了同种类型的函数的抽象模板,该函数模板与类型无关,在使用时通过传入的参数类型产生函数的特定类型版本。

函数模板语法

template <typename T1,typename T2,……>

返回值类型 函数名(参数列表){}

注意:两条语句需要按顺序写在一起

示例

我们可以定义一个swap函数的模板,在调用swap时,编译器自动根据传入参数的类型推断T1的类型,之后不管是什么类型的数据需要交换都可以通过swap函数。

template<typename T1>
void swap(T1& a, T1& b)
{T1 temp = a;a = b;b = temp;
}

细节注意:typename是用来定义模板参数关键字,也可以使用class,为了区分通常使用typename(不能使用struct代替class)。

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化可以分为:隐式实例化和显式实例化。

隐式实例化:让编译器根据实参确定模板参数的实际类型。

就比如上述的swap函数。

但有一点需要注意:编译器一般不会进行类型转换操作,也就是说如果实参是两种类型,那么编译器就会报错。如下:

template<typename T2>
T2 Add(const T2& a, const T2& b)
{return a + b;
}int main()
{int a = 2, b = 3;double c = 1.1, d = 2.1;//编译器报错,实参类型不同cout << Add(a, d) << endl;return 0;
}

解决办法有两种:

一种是在传入参数是就将它们强制转换成同一类型,如

cout << Add(a, (int)d) << endl;

第二种方法就是显示实例化。

显式实例化:在函数名后添加<>,<>中指明模板参数的实际类型。

cout << Add<int>(a, d) << endl;

如果传入参数的类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器则会报错。

函数模板的匹配原则

1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函。

template<typename T,typename T1>
T Add(const T& a, const T1& b)
{return a + b;
}//专用加法函数
int Add(const int a,const int b)
{return a + b;
}

2.如果同时满足模板函数与专用函数,在调动时会优先调用非模板函数。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板函数。

3. 模板函数不会自动的类型转换,但普通函数可以进行自动类型转换

2.类模板

在函数模板之后同理可得类模板概念。

具体看类模板的语法

template <typename T1,typename T2,……>

class 类模板名 { // 类内成员定义 };

注意:两条语句需要按顺序写在一起

示例

template<typename T>
class DateType
{
public:DateType(T& date) :_date(date){}void Display();
private:T _date;
};

注意:类模板中函数放在类外进行定义时,需要加模板参数列表,如

template<typename T>
void DateType<T>::Display()
{cout << _date;
}

类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,实例化的类型放在<> 中。类模板名字不是真正的类型,而实例化的结果才是真正的类型。如

DateType<int> a(1);//DateType<int>才是类型
DateType<double> b(3.14);//DateType<double>才是类型


总结

本位为【C++闯关】系列的第三篇内容,先是介绍了友元相关内容,紧接着补充了C++程序的内存布局,再然后介绍了静态成员与动态成员,最后讨论了模板相关。

整理不易,希望对你有所帮助。

读完点赞,手留余香。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94224.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94224.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94224.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 爬虫教程 | 豆瓣 TOP250 数据抓取与分析实战

一、项目背景与数据价值豆瓣TOP250是影视行业的重要榜单&#xff0c;具有以下数据价值&#xff1a;评分与评价人数&#xff1a;衡量电影市场热度&#xff1b;导演与演员信息&#xff1a;分析人才价值与影视趋势&#xff1b;类型 / 地区 / 年份&#xff1a;洞察电影类型与年代变…

第04章 SPSS简介与数据库构建

参考&#xff1a;SPSS实战与统计思维 - 武松编著 - 微信读书 4.1 SPSS简介 发展历史 全称Statistical Product and Service Solutions&#xff0c;由美国斯坦福大学三位研究生于1968年开发。 对比其他软件成立时间&#xff1a;SAS&#xff08;1976年&#xff09;、Stata&…

【ABAP4】数据字典

ABAP数据字典ABAP数据字典概述数据字典的基本对象域数据元素表类型系统创建自定义透明表创建自定义结构锁对象ABAP数据字典概述 ABAP数据字典是SAP定义和管理数据的工具&#xff0c;包含了程序使用的所有对象&#xff0c;数据字典中包括数据库表、视图、数据类型、域、搜索帮助…

不知道Pycharm怎么安装?Pycharm安装教程(附安装包)

Pycharm安装教程&#xff08;附安装包&#xff09;获取方式&#xff1a;python开发工具包丨夸克网盘-资源免费下载 有位朋友刚开始学习python&#xff0c;不知道Pycharm要怎么安装&#xff0c;于是问我要一个安装教程。 先介绍一下Pycharm吧&#xff0c;PyCharm是一款python开…

在 Docker 容器中查看 Python 版本

博客目录前言方法一&#xff1a;交互式进入容器查看方法二&#xff1a;启动时直接执行命令方法三&#xff1a;启动后使用 exec 执行命令方法四&#xff1a;直接运行并查看版本&#xff08;容器退出&#xff09;方法比较与选择指南实际应用中的注意事项进阶技巧批量检查多个镜像…

React:Umi + React + Ant Design Pro的基础上接入Mock数据

为什么需要Mock数据 前端开发依赖后端接口时的阻塞问题 独立开发和测试的需求 快速迭代和原型验证的重要性 当前版本及框架 React18 Umi 4.0 Ant Design Ant Design Pro 其实这些都不重要&#xff0c;主要是有Umijs&#xff0c;因为Umijs具有开箱即用Mock功能的能力&#…

VMware centos磁盘容量扩容教程

目录前言相关概念磁盘磁盘分区文件系统挂载点物理卷、VG&#xff08;卷组&#xff09;、LV&#xff08;逻辑卷&#xff09;、LVM&#xff08;逻辑卷管理&#xff09;解决方案前言 这篇博客主要分享我在VM中通过docker搭建dify大模型应用平台时&#xff0c;遇到了分配的磁盘容量…

kubernetes中的认证和授权

一 kubernetes API 访问控制Authentication&#xff08;认证&#xff09;认证方式现共有8种&#xff0c;可以启用一种或多种认证方式&#xff0c;只要有一种认证方式通过&#xff0c;就不再进行其它方式的认证。通常启用X509 Client Certs和Service Accout Tokens两种认证方式。…

雅菲奥朗SRE知识墙分享(四):『AI已开始重塑劳动力市场,美国年轻科技从业者首当其冲』

近日&#xff0c;据《商业内幕》报道&#xff0c;AI正在重塑美国就业市场&#xff0c;年轻的科技从业者正首当其冲地感受到冲击。高盛首席经济学家Jan Hatzius在本周一撰文指出&#xff1a;“AI 确实开始在各类数据中显现出更加明显的迹象。”据高盛的分析&#xff0c;科技行业…

Python爬虫入门指南:从零开始的网络数据获取之旅

文章目录前言1. 什么是网络爬虫&#xff1f;2. 爬虫的伦理与法律边界3. Python爬虫的基本工具库3.1 Requests&#xff1a;HTTP请求库3.2 Beautiful Soup&#xff1a;HTML/XML解析库3.3 lxml&#xff1a;高效XML/HTML解析器3.4 Selenium&#xff1a;自动化浏览器工具4. 第一个爬…

说说你对JVM的垃圾回收机制的理解?

Java 虚拟机&#xff08;JVM&#xff09;的垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;机制是自动管理内存的核心&#xff0c;其核心目标是识别并回收不再被使用的对象所占用的内存&#xff0c;避免内存泄漏和溢出。以下从垃圾判断方法、垃圾回收算法和…

兑换汽水瓶

实现代码&#xff1a;public static void main(String[] args) {Scanner in new Scanner(System.in);while (in.hasNextInt()) {int n in.nextInt();if (n 0) {break;}System.out.println(n / 2);}}

结合 Flutter 和 Rust 的跨平台开发方案

结合 Flutter 和 Rust 的跨平台开发方案 1. 核心思想 本方案的核心思想是Flutter 负责 UI,Rust 负责逻辑 。Flutter 作为一个成熟的 UI 框架,专注于渲染流畅、跨平台一致的用户界面。而将那些对性能、安全和并发有高要求的复杂业务逻辑、计算密集型任务或底层系统操作,全部…

理想汽车智驾方案介绍 2|MindVLA 方案详解

一、引言 MindVLA 主要包括空间智能模块、语言智能模块、动作策略模块、强化学习模块&#xff0c;这些模块分别有以下功能&#xff1a; 空间智能模块&#xff1a;输入为多模态传感器数据&#xff0c;使用 3D 编码器提取时空特征&#xff0c;然后将所有传感器与语义信息融合成…

计算机网络基础(三) --- TCP/IP网络结构(运输层)

运输层1. 概述和运输服务运输层协议为运行在不同主机上的应用进程之间提供了逻辑通信功能, 运输层协议是在端系统中而不是路由器中实现的, 网络应用程序可以调用多种运输层协议, 如因特网的两种协议: TCP 和 UDP ,每种协议都能为调用的应用程序提供一组不同的运输层服务1.1 运输…

JdbcTemplate和MyBatis的区别

在 Java 后端开发中&#xff0c;JdbcTemplate&#xff08;Spring 框架提供&#xff09;和 MyBatis&#xff08;持久层框架&#xff09;都是用于简化数据库操作的工具&#xff0c;但它们的设计理念、使用方式、灵活性和适用场景有显著差异。下面从核心定位、核心特性、使用方式、…

埃氏筛|树dfs|差分计数

lc525把数组里的0换成-1&#xff0c;求子数组和为零的最长长度用哈希表记录前缀和首次出现的位置通过找相同前缀和的位置差得出最长的0和1数量相等的子数组长度。class Solution { public:int findMaxLength(vector<int>& nums) {unordered_map<int,int>hashta…

(JVM)Java 对象创建的完整过程

在日常开发和面试中&#xff0c;经常会被问到 “Java 中对象是如何被创建的&#xff1f;” 表面上只是一个 new 关键字&#xff0c;但 JVM 在幕后完成了一系列复杂操作。 可以总结为以下 六大步骤&#xff1a;类加载检查 → 分配内存 → 内存清零 → 设置对象头 → 执行构造函数…

数据库优化提速(三)JSON数据类型在酒店管理系统搜索—仙盟创梦IDE

在 MySQL 中&#xff0c;JSONB 类型&#xff08;MySQL 中实际为 JSON 类型&#xff0c;功能类似 PostgreSQL 的 JSONB&#xff0c;支持高效的 JSON 数据存储和查询&#xff09;非常适合存储半结构化数据&#xff0c;例如酒店入住客人的复杂信息&#xff08;包含客人基本信息、入…

小程序全局状态管理:使用MobX进行跨组件数据共享详解(九)

一、定义全局数据共享&#xff08;又叫&#xff1a;状态管理&#xff09;是为了解决组件之间数据共享的问题&#xff1b;全局数据共享方案&#xff1a;VueX、Redux、MobX等&#xff1b;二、小程序全局数据共享方案使用mobx-miniprogram配合mobx-miniprogram-bindings实现全局数…