C++语法基础-复数类与字符串类
C++ programs 代码的基本形式(以Complex class为例)
头文件与类声明
在写C++项目时,一般将类声明和实现分为两部分存储,即.h
和.cpp
文件中..cpp
文件中要包含#inlcude
他的声明头文件
⚠注意:
在
.cpp
文件中,自己的头文件一般用引号,而引用标准库文件则用尖括号:
1
2
头文件的防御性声明
在大型项目中,一个写好的类声明文件可能会被引用到程序的各个部分,而有一种规范安全的写法可以解决程序四处引用导致类重复声明的问题
示例:
1 |
|
namespace命名空间
namesapce主要的用途是免去每个函数之前都加一个类名,举个栗子:
1 | //使用命名空间 |
inline方式书写类
对于某一个C++类,我们可以将其声明和定义直接写在一起(就是像C语音中一串写下来一样),示例:
1 | class complex |
像如上内容中,在类定义中直接将函数具体实现写在{...}
中就是inline
写法
⚠注意:
使用
inline
方式书写的函数编译之后不一定就是真正的inline
函数,该函数只会成为一个inline
候选,编译器会基于其复杂程度最终确定其是否为真正的inline
,就比如上述类中的两个double
函数,其函数内容非常简单,编译器一般会将其编译为真正的inline
.
除此之外也可以向如下写法在类外定义函数,编译器检测到inline
关键字后会将该函数与声明编译到一块
1 | inline double |
access level(访问级别)关键字
在C++中,**访问级别(access level)**用于控制类成员(属性和方法)的可见性和可访问性,这是封装(encapsulation)的核心机制。C++提供了三个关键字来定义访问级别:public
、protected
和 private
。
public
(公有成员)
- 作用:在任何地方都可以直接访问。
- 使用场景
- 类的接口(供外部调用的方法)。
- 需要被全局访问的常量或工具函数。
private
(私有成员)
- 作用:仅在类内部或**友元(friend)**中可访问,外部代码无法直接访问。
- 设计目的:隐藏实现细节,防止外部意外修改数据。
protected
(保护成员)
- 作用:在类内部和**派生类(子类)**中可访问,外部代码不可访问。
- 设计目的:支持继承时的成员共享,同时限制外部访问。
关键字 | 类内部 | 子类 | 外部代码 | 友元 |
---|---|---|---|---|
public |
✔ | ✔ | ✔ | ✔ |
protected |
✔ | ✔ | ✖ | ✔ |
private |
✔ | ✖ | ✖ | ✔ |
- 默认访问级别:
- class:成员默认是
private
。 - struct:成员默认是
public
(设计初衷是兼容C的数据结构)。
- class:成员默认是
- 友元(friend):
- 通过
friend
关键字,可以允许特定函数或类突破访问限制(慎用,破坏封装性)。
- 通过
- 继承时的访问控制:
- 派生类继承时可通过
public
、protected
、private
继承改变基类成员的访问权限(例如:class Derived : private Base
)。
- 派生类继承时可通过
构造函数与析构函数
在C++中,构造函数(Constructor)和析构函数(Destructor)是类的特殊成员函数,分别用于对象的初始化和清理。它们是面向对象编程中资源管理的关键机制。
还是以之前的代码为例子:
1 | complex(double r = 0, double i = 0) : re(r), im(i) { ... } |
-
使用初始化列表(
: re(r), im(i)
)直接初始化成员变量(比在函数体内赋值更高效)。 -
是参数化构造函数,同时也是一个默认构造函数(因为所有参数都有默认值
0
)。
⏰Tips:
由于我这里写的类是不带指针的,不需要显性的定义析构函数(自带的就够用),带指针的类需要手动释放资源.
参数传递与返回值
const member function(常量成员函数)
const
是C++中一个非常重要的关键字,它用于定义常量、保护数据不被修改,并在编译时强制执行不变性规则。
还是以上面的示例:
1 | double real() const{return re;} |
这样写的意义在于保证程序无论在哪种情况下都能正常使用
比如下面的两种调用方式:
1 | //方式一 |
如果在类的定义中我们没有将const写在函数内容之前,那么方法二就会报错.
函数传参:pass by value与pass by reference(to const)
在C++中,函数参数传递主要有两种方式:值传递和引用传递。如下示例:
1 | void increment1(int x) { |
值传递 (Pass by Value):将实参的副本传递给函数,函数内对参数的修改不会影响原始数据。
- 优点:
- 简单直接
- 不会意外修改原始数据
- 线程安全(每个线程有自己的副本)
- 缺点:
- 对于大型对象(如类、结构体),复制开销大
- 无法通过参数返回额外信息
引用传递 (Pass by Reference to const):将实参的别名传递给函数,避免了复制开销。const
引用还能保证原始数据不被修改。
- 优点:
- 无复制开销,性能高
- 可以修改原始数据(非
const
引用) const
引用既保证效率又保证安全性
- 缺点:
- 非
const
引用可能意外修改原始数据 - 比值传递稍复杂
- 非
特性 | 值传递 | 引用传递 | const引用传递 |
---|---|---|---|
复制开销 | 有(完整复制) | 无(传递引用) | 无(传递引用) |
能否修改原始数据 | 不能 | 能 | 不能 |
线程安全 | 安全(独立副本) | 不安全 | 安全 |
典型用途 | 小型简单数据类型 | 需要修改的参数/输出参数 | 大型只读对象 |
返回值传递:retrun by value与return by reference(to const)
特征与用法与上面参数传递部分基本一样,这里就不再赘述,需要注意的点是,返回引用需要保证该值的生命周期,如果无法确保生命周期可能引起灾难性的bug
1 | // 绝对不要这样写! |
friend友元
友元是C++中一种打破封装性的特殊机制,它允许特定的非成员函数或其他类访问当前类的私有(private)和保护(protected)成员。
1 | class Engine; // 前向声明 |
⚠注意
有一个特殊写法可以理解为:相同class的各个objects互为friends(友元)
ps: 上面的解释是侯捷老师上课的时候给出的,查阅了的一些资料发现好像并不是这样的.
如下示例:
1 | class complex { |
此处在对象c2
中直接访问了c1
的私有成员,这是C++访问控制机制的一个重要特性。
C++的访问控制(
private/protected/public
)是类级别的,而不是对象级别的。这意味着:
- 类的成员函数可以访问该类所有对象的私有成员(包括通过参数传入的其他对象)。
- 这种设计是为了让同类对象之间能高效协作,同时对外部代码保持封装性。
对比
场景 | 能否访问私有成员? | 原因 |
---|---|---|
同类成员函数访问其他对象 | ✅ 可以 | 访问权限基于类(complex::func 可以访问任何complex 对象的私有成员) |
外部普通函数 | ❌ 不能 | 非成员函数无特权 |
友元函数 | ✅ 可以 | 被类显式授权 |
派生类成员函数 | ❌ 不能(除非是protected成员) | 派生类不能访问基类私有成员 |
操作符重载
操作成员函数
如下代码示例:
1 | inline complex& __doapl(complex* ths, const complex& r) { |
⚠注意
所以成员函数一定带着一个隐藏的参数
this
,但是不可以在程序中写出来,否则会报错.
所以其实在编辑器看来我们的运算符重载函数为:
1 | inline complex& complex::operator+=(this,const complex& r) { |
⚠注意
C++允许重载大多数运算符(如
+
,-
,<<
,==
),但不能重载以下运算符:
1 . .* :: ?: sizeof # ##
操作非成员函数与临时对象
1 | inline complex operator + (const complex& x, const complex& y) { |
如上代码可以同时处理多种复数加法运算的情况
⚠注意
上面的函数绝对不可以return by reference,因为,他们返回的必定是local object
typename( .. , ..)
的写法为定义一个临时对象.
String class
Big Three
相比上面的复数类,下面重写的String class需要特别注意三个特殊函数 拷贝复制
,拷贝构造
,析构函数
1 | class String |
ctor和dtor(构造函数 和 析构函数)
class中如果有指针,那么该类的变量中多半使用了动态内存分配,要再析构函数中将该内存释放掉,否则会造成内存泄漏
1 | inline |
copy ctor和copy op= (拷贝构造和拷贝赋值)
⚠Caution !
class with pointer members类中存在指针变量成员,则必须为其书写特殊的
copy ctor
和copy op =
浅拷贝和深拷贝
如果指针类的变量直接使用默认的copy,则会造成两个变量同时指向一个地址(其实就是别名),而且原本内存泄漏掉
1 | inline |
1 | inline |
输出cout
全局函数,在cout
的定义部分重载<<
符号,如下图所示:
1 |
|
class template类模板
1 | template<typename T> |
类模板可以使代码膨胀,一次书写,多次使用.
function template函数模板
1 | template<class T> |
对于上面的函数模板使用,编译器首先会对function template进行参数推导(Argument Deduction),参数推导的结果是T
为stone
,于是调用stone::operator<
,如果该类没有实现<
的重载则会报错.