|
1 | 1 | +++ |
2 | 2 | date = '2025-09-29T08:25:14+08:00' |
3 | | -draft = true |
4 | | -title = 'Learncpp_5' |
| 3 | +draft = false |
| 4 | +title = 'cpp学习笔记(5)' |
5 | 5 | +++ |
6 | 6 |
|
7 | 7 | ## 枚举 |
@@ -159,4 +159,182 @@ int main() |
159 | 159 |
|
160 | 160 | > 为什么`getter`不能像上面一样反过来? |
161 | 161 |
|
162 | | -因为在类内部的存储对象就是真正的`string`,给出它的引用使得外部访问的类型明确,不会造成歧义;同时也提示这个引用的生命周期是明确的————跟成员变量一致。 |
| 162 | +因为在类内部的存储对象就是真正的`string`,给出它的引用使得外部访问的类型明确,不会造成歧义;同时也提示这个引用的生命周期是明确的————跟成员变量一致。 |
| 163 | + |
| 164 | +### 构造函数 |
| 165 | + |
| 166 | +构造函数用于初始化类,在此我们可以手动指定成员变量如何初始化。 |
| 167 | + |
| 168 | +#### 转化构造函数 |
| 169 | + |
| 170 | +转化构造函数其实就是特殊的构造函数,其只拥有一个参数。如果传入一个该参数类型的变量,那么就会自动把这这个变量转化成类。 |
| 171 | + |
| 172 | +#### 默认构造函数 |
| 173 | + |
| 174 | +```cpp |
| 175 | +class Foo{ |
| 176 | + int m_a; |
| 177 | + int m_b{}; |
| 178 | + |
| 179 | + public: |
| 180 | + Foo () = default; //可以显式的声明构造函数为默认构造函数,此时m_a会被默认初始化为0。 |
| 181 | + Foo () {} //用户自己初始化,但是空初始化,此时m_a不会被初始化,是垃圾值。 |
| 182 | +} |
| 183 | +``` |
| 184 | +
|
| 185 | +#### 委托构造函数 |
| 186 | +
|
| 187 | +在一个构造函数的`:`后面写上参数更多的重载构造函数,可以用另一个构造函数来初始化,但是此构造函数就不能再初始化成员变量。 |
| 188 | +
|
| 189 | +尽量不要使用太多的构造函数。 |
| 190 | +
|
| 191 | +#### 复制构造函数 |
| 192 | +
|
| 193 | +复制构造函数的参数必须是引用,要不然就会发生无限递归的调用复制函数。 |
| 194 | +
|
| 195 | +复制构造函数不应用于复制以外的意图,因为编译器可能会发生复制省略(copy elision) |
| 196 | +
|
| 197 | +### explicit关键字 |
| 198 | +
|
| 199 | +禁止隐式转换:从单个参数隐式转换成类对象;从列表`{xxx,xxx}`转换成类对象。 |
| 200 | +
|
| 201 | +在具有单个参数的构造函数前加入,避免编译器执行隐式转换。 |
| 202 | +
|
| 203 | +由于C++对于用户定义的转换,只允许转换一次,所以下面的代码会报错: |
| 204 | +
|
| 205 | +```cpp |
| 206 | +#include <iostream> |
| 207 | +#include <string> |
| 208 | +#include <string_view> |
| 209 | +
|
| 210 | +class Employee |
| 211 | +{ |
| 212 | +private: |
| 213 | + std::string m_name{}; |
| 214 | +
|
| 215 | +public: |
| 216 | + Employee(std::string_view name) |
| 217 | + : m_name{ name } |
| 218 | + { |
| 219 | + } |
| 220 | +
|
| 221 | + const std::string& getName() const { return m_name; } |
| 222 | +}; |
| 223 | +
|
| 224 | +void printEmployee(Employee e) // has an Employee parameter |
| 225 | +{ |
| 226 | + std::cout << e.getName(); |
| 227 | +} |
| 228 | +
|
| 229 | +int main() |
| 230 | +{ |
| 231 | + // 此时要经历两次转换:C-Style string -> string_view -> class Employee |
| 232 | + printEmployee("Joe"); // compile error |
| 233 | + |
| 234 | + // 修正: |
| 235 | + // 方法1: |
| 236 | + // using std::literals; |
| 237 | + // printEmployee("Joe"sv); |
| 238 | + // 方法2: |
| 239 | + // printEmployee(Employee{"Joe"}); |
| 240 | +
|
| 241 | +
|
| 242 | + return 0; |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +不对复制/移动构造函数使用`explicit`,因为他们不执行隐式转换。 |
| 247 | + |
| 248 | +如果转换的时候两者等效且零开销,可以不使用`explicit`。 |
| 249 | + |
| 250 | +比如: |
| 251 | + |
| 252 | +1. `const char*` -> `string_view` |
| 253 | +2. `string` -> `string_view` |
| 254 | + |
| 255 | +### constexpr问题 |
| 256 | + |
| 257 | +从C++14开始,`constexpr`修饰函数仅仅是作为编译时求值的提示,如果传入的变量不是一个`constexpr`,那么这个函数就具有运行时的上下文,`constexpr`修饰就不起作用。 |
| 258 | + |
| 259 | +对于`struct`,其作为一个聚合体,默认的构造函数无须加入`constexpr`就可以用它初始化一个类对象。但是对于`class`,就必须`public`,同时手动指定构造函数是`constexpr`,比如: |
| 260 | + |
| 261 | +```cpp |
| 262 | +Foo { |
| 263 | + public: |
| 264 | + constexpr Foo = default; |
| 265 | +} |
| 266 | +``` |
| 267 | + |
| 268 | +而`constexpr`修饰一个类对象的时候,如果涉及到的函数(构造函数、`setter`以及使用到的成员函数)都有`constexpr`修饰,表示这个类具有编译时的上下文,会让此对象成为一个编译时常量。在编译时如果此对象是用临时对象初始化的,对临时对象是可以修改的,但是一旦这个对象初始化完毕,那么就不再可以修改。 |
| 269 | + |
| 270 | +后续要访问此`constexpr`对象,那么所有的函数`()`后,都必须有`const`,保证不修改此对象。 |
| 271 | + |
| 272 | +参考(learncpp - 14.17)[https://www.learncpp.com/cpp-tutorial/constexpr-aggregates-and-classes/] |
| 273 | + |
| 274 | +### this指针 |
| 275 | + |
| 276 | +`this`指针是一个`const`指针(顶层),指向当前操作的类。 |
| 277 | + |
| 278 | +`this`指针出现比引用早,不然它多少是个引用。 |
| 279 | + |
| 280 | +### 成员函数类外定义 |
| 281 | + |
| 282 | +成员函数可以在类外定义,要加上域访问解析符`::`。如果是类的正下方(`.h`中),前面加`inline`关键字以防止重复包含;如果是对应的cpp中,无须加`inline`。 |
| 283 | + |
| 284 | +默认参数在声明时给出。 |
| 285 | + |
| 286 | +### 类型模板参数 |
| 287 | + |
| 288 | +可以在类指定类型模板参数,不过如果成员函数先声明,然后在类外定义,那么需要单独再指定一次模板参数。 |
| 289 | + |
| 290 | +模板类外的成员函数要紧挨着类定义的下面。 |
| 291 | + |
| 292 | +注意: |
| 293 | + |
| 294 | +1. 所有的模板函数都是默认内联的,所以就算在类外定义,已经隐式`inline`了。 |
| 295 | +2. 类外定义的函数的类型模板参数必须与类的一致。 |
| 296 | + |
| 297 | +传入类模板的参数,无须加`<T>`,因为已经在`Pair<T>::`作用域中了。 |
| 298 | + |
| 299 | +```cpp |
| 300 | +template <typename T> |
| 301 | +bool Pair<T>::isEqual(const Pair& pair) // note the parameter has type Pair, not Pair<T> |
| 302 | +{ |
| 303 | + return m_first == pair.m_first && m_second == pair.m_second; |
| 304 | +} |
| 305 | +``` |
| 306 | + |
| 307 | +### 静态成员变量 |
| 308 | + |
| 309 | +静态成员变量在所有实例化的对象都可用,具有相同的值,在未实例化的时候也可以使用,直接用类名和域访问解析符访问。 |
| 310 | + |
| 311 | +声明的时候,在类内加`static`关键字,类外定义不能加关键字。 |
| 312 | + |
| 313 | +位置:直接在类后面/类对应的`cpp`,头文件中可以加`inline`。 |
| 314 | + |
| 315 | +只有静态成员变量可以自动推断,普通的不允许。 |
| 316 | + |
| 317 | +用途:一个根据数量递增的ID |
| 318 | + |
| 319 | +### 静态成员函数 |
| 320 | + |
| 321 | +静态成员函数用于访问静态全局变量。 |
| 322 | + |
| 323 | +有替代品: |
| 324 | + |
| 325 | +- 命名空间:没有访问控制 |
| 326 | +- 静态全局类对象 |
| 327 | + |
| 328 | +### 友元 |
| 329 | + |
| 330 | +在被访问的类中声明,从而使得外部的类/函数能够访问`private`和`protected`的对象。 |
| 331 | + |
| 332 | +#### 友元函数 |
| 333 | + |
| 334 | +在类中声明,自动成为非成员的函数,类外定义。 |
| 335 | + |
| 336 | +此项特性对于运算符重载非常有用。 |
| 337 | + |
| 338 | +#### 友元类 |
| 339 | + |
| 340 | +直接在类内定义。 |
0 commit comments