当前位置: 首页 > news >正文

c++primer类详解

类的基本思想是数据抽象和封装。

数据抽象是依赖接口和实现分离的编程技术。

1. 定义抽象数据类型

1.1 设计Sales_data类

  • 成员函数的声明必须在类内部,定义可以在内部或外部
  • 作为接口的非成员函数,如print、read,声明定义都在类的外部。
  • 定义在类内部的函数都是隐式的inline函数
  • 调用一个成员函数时,隐式初始化this指针
  • 任何自定义名为this的参数或者变量都是非法的
  • const成员函数
    • const成员函数:在参数列表后加上const关键字的函数
    • const的作用是修改隐式this指针的类型
    • 默认情况下,this的类型是指向类型非常量的常量指针。因此,不能将this绑定在一个非常量对象上(不能把this绑定到其他对象),所以也不能在常量对象上调用普通成员函数(不能用const 对象访问普通成员函数)。
    • const成员函数提高了函数灵活性
  • 常量对象,以及常量对象的引用或指针只能调用常量成员函数。
  • 编译器分两步处理类。
    • 1.编译成员声明。
    • 2.所有成员声明编译完后,编译成员函数体。因此,成员声明出现在成员函数体后,编译器也可以正常编译
  • 在类外定义函数体
    • 需要在函数名前加上类名::,在类名之后剩余的代码位于作用域之内
    • 若返回类型也是在类内声明的,就需要在函数名和返回类型前都加上类名::。
    • 若在类内声明成了const成员函数,在外部定义时,const关键字也不能省略。
  • 若需要返回类本身,使用return *this

1.2 定义类相关的非成员函数

  • 类相关非成员函数:属于类的接口,但是不属于类本身。
  • 通常把函数声明和定义分开。和类声明在同一头文件内。
  • 通常情况下,拷贝一个类其实是拷贝其成员。(若想拷贝执行其他操作,查阅拷贝赋值函数)

1

2

Sale_data s1;

Sale_data s2=s1;//s2拷贝了s1的成员

1.3构造函数

  • 构造函数的任务是初始化类对象的数据成员
  • 只要类对象被创建,一定会执行构造函数
  • 构造函数名与类名相同,并且没有返回类型,其他与普通函数相同。
  • 构造函数不能声明成const
  • 默认构造函数
    • 默认构造函数无需任何实参
    • 若没有为类显式定义任何构造函数,编译器隐式构造一个合成的默认构造函数。
    • 合成的默认构造函数按照如下规则初始化类成员
      • 若存在类内初始值,用它来初始化成员
      • 否则,默认初始化成员
    • 某些类不能依赖合成的默认构造函数
      • 若类包含内置类型或复合类型成员,只有当这些值全被赋予了类内初始值时,这个类才适合使用合成的默认构造函数。
      • 若类a包含一个成员类b,若b没有默认构造函数,则编译器无法为a构造正确的默认构造函数
      • 若定义了其他构造函数,则编译器不会构造默认初始函数

1

2

3

4

5

6

7

classA{

//定义了一个实参为string的构造函数

//此时,编译器不会合成默认构造函数

A(std::string a){}

}

A a;//错误,没有默认构造函数

A a1(std::string("小黑"));//只能用string参数

  • 参数列表后加上 =defualt表示要求编译器生成默认构造函数
  • =defualt可以和声明一起出现在类内,也可以作为定义出现在类外。

若在类内部,则默认构造函数时内联的,若在类外部,默认不是内联的。

1

2

3

4

classA{

A()=defualt;

}

A a;//正确,编译器生成默认构造函数

  • 构造函数初始值列表
    • 存在编译器不支持类内初始值,这样的话默认构造函数不适用(因为默认构造函数使用类内初始值初始化类成员),这时应该使用构造函数初始值列表。
    • 函数初始值列表是参数列表如下所示(冒号以及冒号和花括号间的代码::bookNo(s))
    • 构造函数不应该轻易覆盖掉类内初始值,除非新赋的值与原值不同在
    • 构造函数的过程中,没有出现在函数初始化列表中的成员将被执行默认初始化

1

2

3

4

5

6

7

classSales_data{

Sales_data(conststd::string &s,unsigned n,doublep):

bookNo(s),units_sold(n),revenue(p*n){}

//当编译器不支持类内初始值时,可用如下方法定义

Sales_data(conststd::string &s):

bookNo(s),units_sold(0),revenue(0){}

}

  • 在类外部定义构造函数,要声明是哪个类的构造函数,在函数名前加上类名::

1

2

3

Sales_data::Sales_data(std::istream cin){

read(cin,*this);

}

1.4 拷贝、赋值和析构

  • 编译器会为类合成拷贝、赋值和销毁操作。
  • 编译器生成的版本对对象的每个成员执行拷贝、赋值和销毁操作

2 访问控制和封装

  • 访问说明符

public说明符后的成员在整个程序内可以被访问

private说明符后的成员可以被类的成员函数访问

  • 一个类可以包含0个或多个访问说明符,有效范围到下一个说明符出现为止。
  • class和struct关键字定义类的唯一区别是
    • class在第一个访问说明符出现之前的区域默认是private
    • struct在第一个访问说明符出现之前的区域默认是public

2.1 友元

  • 类可以允许其他类或函数访问他的非公有成员。方法是用关键字friend声明友元。
  • 友元的声明只能在类内部
  • 友元声明的位置不限,最好在类定义开始或结束前集中声明友元。
  • 封装的好处
    • 确保用户代码不会无意间破坏封装对象的状态
    • 被封装的类的具体实现细节可以随时改变
  • 友元在类内的声明仅仅指定了访问权限,并不是一个通常意义的函数声明
    • 若希望类的用户能够调用某个友元函数,需要在友元声明之外再专门对函数进行一次声明
    • 为了使友元对类用户可见,友元声明与类本身防止在同一个头文件中
    • 一些编译器强制限定友元函数必须在使用之前在类的外部声明

2.2 类的其他特性

接下来介绍:类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回*this、如何定义使用类类型、友元类

2.2.1 类成员再探
  • 类别名(类型成员):

在类中定义的类型名字和其他成员一样存在访问限制,可以是public或者private

类别名必须先定义后使用

(回忆:类成员变量可以在类成员函数之后定义,但是在类函数中使用,原因是编译器先编译类成员变量后边一类成员函数)

类型成员通常出现在类开始的地方

1

2

3

4

5

classScreen{

public:

//等价于 using pos = std::string::size_type

typedefstd::string::size_type pos;

}

  • 令成员作为内联函数

定义在类内部的函数是自动inline的,定义在类外部的函数,若需要声明内联函数,要加上inline;inline成员函数也应该和相应的类定义在同一个头文件夹

1

2

3

4

5

6

inline

Screen& Screen::move(pos r,pos c){

pos row = r*width;

cursor = row + c;

return*this;

}

  • 可变数据成员,永远不会是const,即使他是const对象的成员

1

2

3

4

5

6

7

8

classScreen{

publicvoidsome_member()const;

private:

mutablesize_taccess_ctr;//使用mutable声明可变数据成员

}

voidScreen::some_member()const{

++access_ctr;//即使在const成员函数中,仍然可以修改可变数据成员

}

  • 类内初始值使用=的初始化形式或者花括号括起来的直接初始化形式

2.2.2 返回*this的成员函数
  • 注意返回类型是否是引用。是否是引用对函数的使用方法影响很大

1

2

3

4

5

6

7

8

9

10

11

12

inlineScreen &Screen::set(charch){

content[cursor] =ch;

return*this;

}

inlineScreen &Screen ::move(pos r,pos col){

cursor= r * width + col ;

return*this;

}

Screen s(3,2,'');

//move函数返回s本身,所以可以接着调用set函数

//并且move函数返回的是Screen的引用,若返回的不是引用,则会返回一个新的Screen对象

s.move(3,2).set('!');

  • 从const函数返回的是常量引用,在const函数中无法修改类成员变量
  • 使用const函数进行重载

编写函数display打印Screen中的contents,因为只是展示,不需要修改值,所以这应该是一个const函数。

但是希望实现在展示后,能移动光标:s.display().move(2,3)。这要求display返回的值是可以修改的,所以这不应该是const函数。

基于const重载,可以根据Screen对象是否是const来进行重载。

  • 建议多使用do_display这类函数完成实际工作,使公共代码使用私有函数
    • 可以集中修改
    • 没有额外开销

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

classScreen{

public:

Screen* display(std::ostream &os){

do_display(os);

return*this;

}

constScreen* display(std::ostream &os)const{

do_display(os);

return*this;

}

private:

voiddo_display(std::ostream &os)const{

os<<content;

}

}

intmain(){

constScreen cs(3,3,'!');

Screen s(3,3,'.')

cs.display();//因为cs是const的,调用第二个const函数

s.display();//调用第一个非const的函数

}

2.2.3 类类型
  • 每个类定义了唯一的类型,即使成员完全相同,也是不一样的类。

1

2

3

4

5

6

7

8

classA{

intmember;

}

classB{

intmember;

}

A a;

B b = a;//错误!!

  • 不完全类型
    • 类似于函数,类也可以只声明,不定义,这被叫做不完全类型
    • 不完全类型是向程序说明这是一个类名
    • 不完全类型使用环境很有限,只是可以定义指向这种类型的指针或引用,声明(但不能定义)以不完全类型作为参数或返回类型的函数。
  • 类在创建前必须被定义
  • 类的成员不能有类本身(除了后面介绍的static类),但是可以是指向自身的引用或指针

2.2.4 友元再探
  • 一个类制定了其友元类,则友元函数可以访问该类的所有成员
  • 友元关系不存在传递性
  • 每个类自己负责控制自己的友元类或友元函数
    • 定义友元函数的顺序:

有一个screen类,有私有成员content;

有clear函数,可以清除content的内容。

1.先声明clear函数

2.在screen类中将clear函数函数定义为友元函数

3.定义clear函数,使用screen类

  • 定义友元类

有类window,window有私有成员content;友元类 window_mgr需要直接操作content。

  • 正常编写window类,在window类中声明:friend class window_mgr;
  • 正常编写 window_mgr类,可以直接使用window的content
  • 注意将类写在头文件中,要按照如下格式;否则编译会报错重复的类定义

1

2

3

4

#ifndef xxx_H

#define xxx_H

/class定义///

#endif

  • 一个类想把一组重载函数定义为它的友元,需要对这组函数中的每一个进行友元声明。
  • 友元声明仅仅表示对友元关系的声明,但并不表示友元这个函数本身的声明

1

2

3

4

5

6

7

8

9

10

structX{

friendviod f(){/*友元函数可以定义在类的内部,但是我认为这样没有意义*/

X(){f();}//错误,f还没有被定义

voidg();

voidh();

}

voidX::g(){returnf();}//错误,f还没有被定义

voidf();

voidX::h(){returnf();}//正确,f的声明已经在定义中了

};

2.4 类的作用域

  • 定义在类外的方法需要在方法名前使用::说明该方法属于哪一个类,在说明属于的类后,该函数的作用域位于该类内。
    • 即返回类型使用的名字位于类的作用域之外。若返回类型也是类的成员,需要在返回类型前使用::指明返回类型属于的类

1

2

3

4

5

//pos的类型声明在window类中,并且返回类型在类的作用域外,因此要使用window::pos

window::pos window::get_pos(){

//在window::get_pos后的所有代码作用域在类内,所以返回cursor,相当于this->cursor

returncursor;

}

2.4.1 名字查找和类的作用域
  • 类的定义分两步处理
    • 1.编译成员的声明
    • 2.直到类成员全部可见后编译函数体
  • 一般来说,内层作用域可以重新定义外层作用域名字;但在类中若使用了某个外层作用域中的名字,并且该名字表示一种类型,则类不能在之后重新定义该名字
http://www.jsqmd.com/news/723792/

相关文章:

  • 播客内容创作4个核心技巧,帮你稳定产出高质量吸粉内容
  • ARM MPAMSM_EL1寄存器解析与资源隔离技术
  • Prompt工程的反模式:那些让你的AI应用变差的常见错误
  • Oracle 数据库启动失败:ORA-29701、ORA-01565、ORA-17503 故障处理记录_20260429
  • 睡眠编译优化:软件测试从业者的专业效能提升指南
  • 跟着 MDN 学 HTML day_1:(全套原生Input+表单结构拆解)
  • 前端性能优化:JavaScript 性能优化详解
  • 房产看房记录口碑推荐|经筛选优质实用选择整理分享
  • baidupankey:极速一键智能获取百度网盘提取码的全自动解决方案
  • ARM PMSEVFR_EL1寄存器解析与性能监控实践
  • 【技术应用】PLA技术“点亮”蛋白互作,破解动脉粥样硬化新机制!
  • 2026年全国靠谱的网球场地租赁公司推荐,梅江南网球俱乐部上榜 - 工业品网
  • 2026最新鲁大师 6.2最终绿化版,去除无用功能和广告
  • 3步快速上手:用哔哩下载姬downkyi轻松搞定B站视频下载
  • 数据光合作用:软件测试从业者的专业视角
  • 【C++27安全红线】:3类已被标记为deprecated的异常传播模式(含std::exception_ptr隐式转换),9月30日前必须迁移!
  • Kubernetes集群基石:保姆级Containerd配置与CNI网络插件集成指南(含一键脚本)
  • 声定向系统改良设计——大功率集成化声频定向扬声器系统
  • 运维必看:如何用Java Oshi监控Linux服务器性能并接入Prometheus+Grafana
  • SeuratWrappers终极指南:如何在单细胞分析中轻松使用社区扩展工具
  • FDA新政落地,先觉生物类器官引领研发新变革
  • Go语言轻量级HTTP路由库Oatmeal:高性能微服务与API开发实践
  • 秘语盾技术博客:Ledger 设备恢复出厂设置教程
  • 分析2026年杭州靠谱美术集训推荐学校,哪家性价比高 - 工业品网
  • 泛微OA中如何实现,将选中的明细行数据内容,传送给其他系统或是单独存放
  • ADLINK Alder Lake-H COM模块技术解析与工业应用
  • 焦虑冷核聚变:软件测试从业者的技术焦虑与突破之道
  • 零基础药师用药指导入门指南,新手避坑看完就能直接上手
  • ARM异常处理与SMC指令陷阱机制详解
  • 探讨如何与讯灵AI的销售团队取得联系,开启企业数字化转型之旅 - 工业设备