C++ 踩坑日记
Tue Nov 28 2023
# CPP 踩坑日记
本来我是不爱记笔记的,但是没办法啊,这cpp东西有亿点多,而且和之前写的那些语言(C# / Python / TypeScript)完全不能用一个思路去思考,所以感觉还是得稍微记点东西(作业做到什么就记什么
以及你可能会在本篇中看到一堆,把其他语言的特性拉出来与cpp对比的场景(因为咱真的不会cpp喵呜呜呜
## const 修饰符
cppreference里面把const和volatile放在一起,合称cv (const and volatile) type qualifiers,这个const用起来是真的有点难评
比如,在上面这一行中,就出现了两个const
,第一个const
用来修饰string
,也就是返回类型,表明返回的值是一个不可变的string
,第二个const
则修饰函数主体
对返回类型的修饰其实还好理解,一个const SomeType A
就表明这个A
是只读的,类似于C#的readonly
的readonly field的用法,即初始化初始化后不可变。但不同之处在于C#的readonly
一般只能用在class和struct里面作为field的修饰符,在声明时初始化,或者在构造函数/静态构造函数时初始化,cpp的这个const
则相对灵活得多,可以在几乎任何地方使用,比如方法内部声明一个const
的变量(笑死,什么不可变的变量)。而且C#的readonly
更多的是对一个字段的修饰,cpp的const
则更多是对一个类型的修饰。
而第二个const
则是对方法主体的一个限制,标记这个方法不可以修改this
的所指向的对象的字段,也就是只能读取自身的字段的值,而不能修改,CppReference也给这种对于成员方法的cv修饰符进行了解释。(之前都没看到,今天翻C#的文档的时候发现,什么时候又加了这个语法,这下看懂了
现在假设有这样一个字段的声明:
SomeType
定义了两个public
的方法:
那么对于对象实例a
,只能访问a.toString()
而不能访问a.toString2()
,因为toString
方法声明了自己不对对象进行修改,而toString2
没有,因此,如果调用toString2
,编译器会直接报错,即使toString2
本身在实现的时候并没有修改对象本身。
update 1: cpp中的两个同名函数,根据是否被标记为const
,可以分成两个方法重载(overload),如以下代码,实际编译器并不会报错,因为可以视为两个方法的signature不同
const
没记错的话应该还有别的用法,下次再补充,摸了
## 赋值运算符(operator=)重载(overloading)
第一次看到赋值运算符,即operator=
居然是可以重载的时候,感觉有亿点点不可思议,但仔细了解之后可以发现,这其实是因为与我常用的其他语言的一些差异导致的。
在cpp中,this
是一个特殊的指针,是一个指向当前对象的指针。但其本质还是指针,所指向的还是一个内存上的地址,因此在调用赋值运算符operator=
的时候,其实应该理解为对this
所指向内存区域的一个操作,因此存在operator=
的重载并不奇怪,这个operator=
的operand应该为this
所指向的内存区域和一个新的对象实例(通过值或者引用传递)。
但其他语言,比如C#和java中,为什么没有复制运算符的重载呢?我的理解是,C#和java的this
并不是一个指针,而是一种引用,这个引用强绑定到当前对象实例。因此在对变量赋值的时候,其实是和上一个对象实例无关的操作,无法定义一个赋值运算符的重载,因为没办法确定运算符的operand,在C#和java这种managed的语言中,相关的内存管理操作全部已经由gc接管了。
## 指针(pointer)与引用(reference)与智能指针(smart pointer)
这也要分这么细来恶心人是吧算了,好像区别还是挺大的(
### Pointer
### Reference
### Smart Pointer
## 模板 template
template
其实就是cpp对泛型的一种实现,虽然看起来和别的语言的泛型区别有亿点点大。
cpp的template是一种metaprogramming,其实是编译器自动在编译期实现了各个不同参数的实现
template<arg1, arg2, ...args>
- Microsoft Docs
- CppReference: Template parameters and template arguments
- CppReference: Template
- CppReference: Dependent names
### error ld: undefined reference
参考
- StackOverflow: Undefined reference error for template method
- StackOverflow: Why can templates only be implemented in the header file?
- StackOverflow: Error "Undefined reference to operator<<" in C++ code
## friend
keyword
## 继承
### 公有继承、保护继承、自由继承
捏猫猫的,这cpp怎么这么多事
假如一个DerivedClass
继承自一个BaseClass
,公有继承public
、保护继承protected
、私有继承private
可以简单理解为:
原来通过DerivedClass
的实例derivedInstance
访问BaseClass
中的member
可以看成derivedInstance.super.member
(虽然实际用起来不是,但是可以这么理解,而且本质上的实现就是在派生类里面放一个基类对象)
三种继承方式的区别就相当于给derivedInstance
中的这个“super
”加上一个访问修饰符。
对于继承自DerivedClass
的派生类同理。
### cpp的多重继承
多重继承就是一个class
可以同时继承自多个class
,因为cpp没有interface
。多重继承会导致一个问题:class
是有自己的field的,是要占用内存空间的,这就导致在多重继承是,假如多个父类存在一个共同的父类,就会导致编译器创建了多块独立的内存来存储这个共同父类的字段,且如果在子类调用冲突的父类字段,会导致编译器报错。
问题来了,为什么java和C#中没这个问题能?因为有interface
,interface
只约定了对象方法,不涉及对象的字段和内存,而且是单继承,不允许同时继承自多个父类。
### 虚继承
在继承时加上virtual
即为虚继承。虚继承不会创建重复的父类,也就不存在字段冲突的问题。