目录
31. Object Copy & Anonymous Object
32. Separating Declaration from Implementation
33. Avoiding Multiple Inclusion of Header Files
34.Object Pointer & Dynamic Object
36. Passing Objects to Functions
37. Abstraction and Encapsulation
38. The Scope of Members & "this" pointer
39. C++11: Default Member Initializers
40. Constructor Initializer Lists
42. Order of Member Initialization
45. Constant Expression 与 C++11的constexpr
46. Assert and C++11 static_assert
47. Declaration and Definition
48. C++11:Delegation Constructor
49. Immutable Objects and Classes
50. Instance Members and Static Members
57. More Programming Style Guidelines
59. Structured Binding Declaration for Array
60. Structured Binding Declaration for Object Data Members
62. C++11:Constructors in Inheritance
63. Default Constructor in Inheritance
64. Chain of Constructors and Destructors
68. Implementation of Run-time Polymorphism
69. Summarize of Run-time Polymorphism
70. C++11: Using override and final
71. Accessibility (Visibility)
72. Abstract Class and Pure Virtual Function
76. C++17: About File System Library
78. Introduction to the Input and Output Classes
31. Object Copy & Anonymous Object
对象拷贝和匿名对象
-
Class is a Type (类是一种数据类型)。
用类声明一个实体的说法,与定义变量的说法有些不同:用原生数据类型定义变量,用类名定义对象。
1.1. 定义变量的例子: // primitive data type ---> variables
double d1; //未初始化
double d2(3.3);
int x1{2.0}; //error: Narrowing
int x2={4};
auto i{x};
decltype(d1) j;
1.2. 定义对象的例子: // class --->objects
Circle c1; //调用Circle的默认ctor
Circle c2(5.5); //调用Circle的有参ctor
Circle c3{5.5}; // 直接列表初始化,调有参ctor
Circle c4 = {5.5}; // 拷贝列表初始化,调ctor
auto c5 = Circle{2.}; // auto类型推断
decltype(c1) c6; // decltype类型推断
-
Memberwise Copy (成员拷贝)
How to copy the contents from one object to the other?(如何将一个对象的内容拷贝给另外一个对象)
(1) use the assignment operator( 使用赋值运算符) : =
(2) By default, each data field of one object is copied to its counterpart in the other object. ( 默认情况下,对象中的每个数据域都被拷贝到另一对象的对应部分)
Example: circle2 = circle1;
(1) 将circle1 的radius 拷贝到circle2 中
(2) 拷贝后:circle1 和 circle2 是两个不同的对象,但是半径的值是相同的。( 但是各自有一个radius 成员变量)
-
Anonymous Object (匿名对象)
Occasionally, you may create an object and use it only once. (有时需要创建一个只用一次的对象)
An object without name is called anonymous objects. (这种不命名的对象叫做匿名对象)
Example
int main() {
Circle c1 = Circle{1.1};
auto c2 = Circle{2.2}; // 用匿名对象做拷贝列表初始化
Circle c3{}; // 直接列表初始化,调默认Ctor
c3 = Circle{3.3}; // 用匿名对象赋值
cout << "Area is " << Circle{4.2}.getArea() << endl;
cout << "Area is " << Circle().getArea() << endl; // 不推荐
cout << "Area is " << Circle(5).getArea() << endl; // 不推荐
return 0;
}
-
class Replaces struct
The C language has the struct type for representing records. (C语言中使用结构体类型来表示一组数据)
In C++, class has replaced struct (在C++中,结构体已被类取代)
-
Local class & Nested class (局部类和嵌套类)
Local class : a class declared inside a function (局部类是在一个函数中声明的类)
void f(){
class C { // C及其对象只在f()中可用
void g() { // 成员函数必须在C中实现 /* 访问f()的成员受限 ……. */ }
};
C c1, c2;
}
Nested class: a class declared in another enclosing class (嵌套类是在另一个类中声明的类)
class E{
class N { // N及其对象可访问E的成员
/* 声明N的成员 ……. */ }
};
C c1, c2;
}
32. Separating Declaration from Implementation
将声明与实现分离
-
How do we do in C? (在C语言中我们怎么做)
// GetArea.h:
float getArea (float radius);
// GetArea.cpp:
#include "GetArea.h"
float getArea (float radius) {
return radius*radius*3.14;
}
//Main.cpp:
#include "GetArea.h"
using namespace std;
int main() {
cout << getArea(2.0) << endl; return 0;
}
-
Separating Declaration from Implementation (声明与实现分离)
C++ allows you to separate class declaration from implementation. (C++中,类声明与实现可以分离)
(1) .h: 类声明,描述类的结构
(2) .cpp: 类实现,描述类方法的实现
FunctionType ClassName :: FunctionName (Arguments) { //… }
其中,:: 这个运算符被称为binary scope resolution operator(二元作用域解析运算符),简称“域分隔符”
-
Inline Declaration & Inline Function (内联声明与内联函数)
When a function is implemented inside a class declaration, it automatically becomes an inline function. (当函数在类声明中实现,它自动成为内联函数)
class A {
public:
A() = default; //C++11
double f1() { // f1自动称为内联函数
// do something
}
double f2();
};
double A::f2() { // f2不是内联函数
//do something
}
class A {
public:
A() = default; //C++11
double f1();
double f2();
};
double A::f2() {
//do something
}
inline double A::f1() { // f1是内联函数
//do something
}
33. Avoiding Multiple Inclusion of Header Files
避免头文件被多次包含
-
Avoiding Multiple Inclusion of Header Files (避免头文件被多次包含)
C/C++使用预处理指令(Preprocessing Directives)保证头文件只被包含/编译一次
例1:
#ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H // 头文件内容 #endif
例2:
#pragma once // C++03, C90
例3
_Pragma("once") // C++11, C99;
34.Object Pointer & Dynamic Object
对象指针与动态对象
-
Accessing Object Members via Pointers (用指针访问对象成员)
Object pointers can be assigned new object names(对象指针可以指向新的对象名)
Arrow operator -> : Using pointer to access object members (箭头运算符 -> :用指针访问对象成员)
Circle circle1;
Circle* pCircle = &circle1;
cout << "The radius is " << (*pCircle).radius << endl;
cout << "The area is " << (*pCircle).getArea() << endl;
(*pCircle).radius = 5.5;
cout << "The radius is " << pCircle->radius << endl;
cout << "The area is " << pCircle->getArea() << endl;
-
Creating Dynamic Objects on Heap (在堆中创建对象)
Object declared in a function is created in the stack.(在函数中声明的对象都在栈上创建); When the function returns, the object is destroyed (函数返回,则对象被销毁).
To retain the object, you may create it dynamically on the heap using the new operator. (为保留对象,你可以用new运算符在堆上创建它)
Circle *pCircle1 = new Circle{}; //用无参构造函数创建对象
Circle *pCircle2 = new Circle{5.9}; //用有参构造函数创建对象
//程序结束时,动态对象会被销毁,或者
delete pObject; //用delete显式销毁
35. Array of Objects
对象数组
-
Array of Objects (对象数组)
(1) 声明方式1
Circle ca1[10];
(2) 声明方式2
用匿名对象构成的列表初始化数组
Circle ca2[3] = { // 注意:不可以写成: auto ca2[3]= 因为声明数组时不能用auto
Circle{3}, Circle{ }, Circle{5} };
(3) 声明方式3
用C++11列表初始化,列表成员为隐式构造的匿名对象
Circle ca3[3] { 3.1, {}, 5 };
Circle ca4[3] = { 3.1, {}, 5 };
(4) 声明方式4
用new在堆区生成对象数组
auto* p1 = new Circle[3]; auto p2 = new Circle[3]{ 3.1, {}, 5 }; delete [] p1; delete [] p2; p1 = p2 = nullptr;
上述代码第4行若是改为 delete [] p1,会发生什么情况?
运行错误
36. Passing Objects to Functions
对象作为函数参数
-
Objects as Function Arguments (对象作为函数参数) You can pass objects by value or by reference. (对象作为函数参数,可以按值传递也可以按引用传递)
(1) Objects as Function Return Value(对象作为函数参数)
// Pass by value
void print( Circle c ) {
/* … */
}
int main() {
Circle myCircle(5.0);
print( myCircle );
/* … */
}
(2) Objects Reference as Function Return Value(对象引用作为函数参数)
void print( Circle& c ) {
/* … */
}
int main() {
Circle myCircle(5.0);
print( myCircle );
/* … */
}
(3) Objects Pointer as Function Return Value(对象指针作为函数参数)
// Pass by pointer
void print( Circle* c ) {
/* … */
}
int main() {
Circle myCircle(5.0);
print( &myCircle );
/* … */
}
-
Objects as Function Return Value(对象作为函数返回值)
// class Object { ... };
Object f ( /函数形参/ ){
// Do something
return Object(args);
}
// main() {
Object o = f ( /实参/ );
f( /实参/ ).memberFunction();
-
Objects Pointer as Function Return Value(对象作为函数返回值)
// class Object { ... };
Object* f ( /函数形参/ ){
Object* o = new Object(args) // 这是“邪恶”的用法,不要这样做
// Do something
return o;
}
// main() {
Object* o = f ( /实参/ );
f( /实参/ )->memberFunction();
// 记得要delete o
允许的用法
// class Object { ... };
Object* f ( Object* p, /其它形参/ ){
// Do something
return p;
}
// main() {
Object* o = f ( /实参/ );
// 不应该delete o
实践:
尽可能用const修饰函数返回值类型和参数除非你有特别的目的(使用移动语义等)。
const Object* f(const Object* p, /* 其它参数 */) { }
-
Objects Reference as Function Return Value(对象引用作为函数返回值)
// class Object { ... };
Object& f ( /函数形参/ ){
Object o {args};
// Do something
return o; //这是邪恶的用法
}
可行的用法1
// class Object { ... };
class X {
Object o;
Object f( /实参/ ){
// Do something return o;
}
}
可行的用法2
// class Object { ... };
Object& f ( Object& p, /其它形参/ ){
// Do something
return p;
}
// main() {
auto& o = f ( /实参/ );
f( /实参/ ).memberFunction();
实践:
用const修饰引用类型的函数返回值,除非你有特别目的(比如使用移动语义)
const Object& f( /* args */) { }
-
一些高阶问题
传值,传址,传指针,传引用都是骗初学者的。C++中有意义的概念是传值和传引用
Differences between a pointer variable and a reference variable https://stackoverflow.com/a/57492
Difference between passing by reference vs. passing by value? https://stackoverflow.com/a/430958
在为函数传参时, 何时用引用,何时用指针呢?
一般来说,能用引用尽量不用指针。引用更加直观,更少出现意外的疏忽导致的错误。
指针可以有二重、三重之分,比引用更加灵活。有些情况下,例如使用 new 运算符,只能用指针。
关于指针与引用的区别,可以看 CSDN 的【这篇文章】,讲得很细致;在该文中的第5部分,也讲了函数传参时“指针传递”与“引用传递”的差别,但这个解释比较晦涩,需要你有汇编语言或者微机原理或者计算机组成原理方面的知识方能透彻理解。在《深入探索C++对象模型》这本书中也有关与引用的解释
37. Abstraction and Encapsulation
抽象与封装
-
Data Field Encapsulation (数据域封装)
数据域采用public的形式有2个问题
(1) First, data may be tampered. ( 数据会被类外的方法篡改)
(2) Second, it makes the class difficult to maintain and vulnerable to bugs. ( 使得类难于维护,易出现bug)
class Circle {
public:
double radius;
//……
};
// main() {
circle1.radius=5; //类外代码可修改public数据
-
Accessor and Mutator (访问器与更改器)
2.1. To read/write private data, we need get/set function (为读写私有数据,需要get/set函数) (1) get function is referred to as a getter (获取器,or accessor),
(2) set function is referred to as a setter (设置器,or mutator).
2.2. Signature of get function (General form) (get函数的一般原型) returnType getPropertyName()
2.3. Signature of get function (Bool type) (布尔型get函数的原型) bool isPropertyName()
2.4. Signature of set function (set函数的原型) void setPropertyName(dataType propertyValue)
-
Class Abstraction and Encapsulation (类抽象与封装) 3.1. Class abstraction (类抽象)
The process of removing physical, spatial, or temporal details or attributes in the study of objects or systems in order to more closely attend to other details of interest ( 在研究对象或系统时,为了更加专注于感兴趣的细节,去除对象或系统的物理或时空细节/ 属性的过程叫做抽象)
3.2. Class encapsulation (类封装)
A language mechanism for restricting direct access to some of the object's components.( 一种限制直接访问对象组成部分的语言机制)
A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data ( 一种实现数据和函数绑定的语言构造块)
3.3. 总结
抽象: 提炼目标系统中我们关心的核心要素的过程
封装: 绑定数据和函数的语言构造块,以及限制访问目标对象的内容的手段
-
Example: Circle
4.1. Abstraction (圆的抽象)
实际的圆有大小、颜色;数学上的圆有半径(radius)、面积(area)等
抽象的过程是,将我们的关注的东西提取出来,比如:“给定半径r,求面积”
4.2. Encapsulation
我们要限制对radius的访问, 然后用“class”把数据和函数绑定在一起
38. The Scope of Members & "this" pointer
成员作用域与this指针
-
The Scope of Data Members in Class (数据成员的作用域)
The data members are accessible to all constructors and functions in the class. (数据成员可被类内所有函数访问)
Data fields and functions can be declared in any order in a class. (数据域与函数可按任意顺序声明)
-
Hidden by same name (同名屏蔽)
2.1. If a local variable has the same name as a data field: (若成员函数中的局部变量与某数据域同名)
(1) the local variable takes precedence ( 局部变量优先级高:就近原则)
(2) the data field with the same name is hidden. ( 同名数据域在函数中被屏蔽)
2.2. 为避免混淆,不要在类中多次声明同名变量,除了函数参数
-
The this Pointer (this指针)
How do you reference a class’s hidden data field in a function? (如何在函数内访问类中被屏蔽的数据域)? 可以使用 this 关键字
This 关键字的特性
(1) a special built-in pointer ( 特殊的内建指针)
(2) references to the calling object. ( 引用当前函数的调用对象)
-
Simple way to avoid name hidden (避免重名屏蔽的简单方法)
class Circle {
public:
Circle();
Circle(double radius_)
{
//this->radius = radius; radius = radius_;
}
private:
double radius;
public:
void setRadius(double);
//……
};
-
编码规范
-
If the parameter of a member function has the same name as a private class variable, then the parameter should have underscore suffix.
-
若类的成员函数参数与私有成员变量名相同,那么参数名应加下划线后缀
例:
class SomeClass {
int length;
public:
void setLength( int length_ );
39. C++11: Default Member Initializers
类成员的就地初始化
-
What is Default Member Initializers (什么是就地初始化)
In C++03, only static const members of integral types could be initialized in-class (在C++03标准中,只有静态常量整型成员才能在类中就地初始化)
class X {
static const int a = 7; // ok
const int b = 7; // 错误: 非 static
static int c = 7; // 错误: 非 const
static const string d = "odd"; // 错误: 非整型
// ...
};
C++11 was to allow a non-static data member to be initialized where it is declared in its class (C++11标准中,非静态成员可以在它声明的时候初始化)
“就地初始化”的术语的来源有多处:
(1) 就地初始化:《深入理解C++11》
(2) In-class initializer : https://isocpp.org/
(3) default member initializer : https://cppreference.com
-
Examples and Rules (例子和规则)
class S {
int m = 7; // ok, copy-initializes m
int n(7); // 错误:不允许用小括号初始化
std::string s{'a', 'b', 'c'}; // ok, direct list-initializes s
std::string t{"Constructor run"}; // ok
int a[] = {1,2,3}; // 错误:数组类型成员不能自动推断大小
int b[3] = {1,2,3}; // ok
// 引用类型的成员有一些额外限制,参考标准
public:
S() { }
};
40. Constructor Initializer Lists
构造函数初始化列表
-
Constructor Initializer (构造函数初始化)
在构造函数中用初始化列表初始化数据域
ClassName (parameterList)
: dataField1{value1}, dataField2{value2}
{
// Something to do
}
-
Why we need a Constructor Initializer Lists? (为何需要构造函数初始化列表)
A data field is an object type (Object in Object / Embedded Object) (类的数据域是一个对象类型,被称为对象中的对象,或者内嵌对象)
The embedded object must be constructed before the body of ctor is executed (内嵌对象必须在被嵌对象的构造函数体执行前就构造完成)
class Time { /* Code omitted */ }
class Action {
public:
Action(int hour, int minute, int second) {
time = Time(hour, minute, second); //time对象应该在构造函数体之前构造完成
}
private:
Time time;
};
Action a(11, 59, 30);
41. Default Constructor
默认构造函数
-
Default Constructor (默认构造函数)
A default constructor is a constructor which can be called with no arguments (either defined with an empty parameter list, or with default arguments provided for every parameter) (默认构造函数是可以无参调用的构造函数,既可以是定义为空参数列表的构造函数,也可以是所有参数都有默认参数值的构造函数)
class Circle1 {
public:
Circle1() { // 无参数
radius = 1.0; /*函数体可为空*/
}
private:
double radius;
};
class Circle2 {
public:
Circle2(double r = 1.0) // 所有参数都有默认值
: radius{ r } {
}
private:
double radius;
};
https://en.cppreference.com/w/cpp/language/default_constructor
-
The Role of Default Constructor (默认构造函数的角色)
2.1. If object type members/embedded objects are not initialized explicitly (若对象类型成员/内嵌对象成员没有被显式初始化)
(1) the default constructor of the embedded object is automatically invoked. ( 该内嵌对象的无参构造函数会被自动调用)
(2) If a default constructor of the embedded object does not exist, a compilation error will be reported. ( 若内嵌对象没有无参构造函数,则编译器报错)
2.2. You can use the Constructor Initializer to construct the object manually (你也可以在初始化列表中手工构造对象)
2.3. 若类的数据域是一个对象类型(且它没有无参构造函数),则该对象初始化可以放到构造函数初始化列表中
42. Order of Member Initialization
成员的初始化次序
-
How to Initialize the Object/Class Members? (如何初始化对象/类成员)
Default Member Initialization (就地初始化)
Constructor Initialization List (构造函数初始化列表)
Assign Values to the members in Ctor Body (在构造函数体中为成员赋值)。注意,这个不是初始化,而是赋值。
执行次序: 就地初始化 à Ctor 初始化列表 à 在Ctor 函数体中为成员赋值
哪个起作用(初始化/赋值优先级): 在Ctor 函数体中为成员赋值 > Ctor 初始化列表 > 就地初始化
-
Default Member Initializer is Ignored(就地初始化被忽略)
If a member has a default member initializer and also appears in the member initialization list in a constructor, the default member initializer is ignored. (若一个成员同时有就地初始化和构造函数列表初始化,则就地初始化语句被忽略不执行)
#include <iostream>
int x = 0;
struct S {
int n = ++x; // default initializer
S() { } // 使用就地初始化(default initializer)
S(int arg) : n(arg) { } // 使用成员初始化列表
};
int main() {
std::cout << x << '\n'; // 输出 0
S s1;
std::cout << x << '\n'; // 输出 1 (default initializer ran)
S s2(7);
std::cout << x << '\n'; // 输出 1 (default initializer did not run)
}
43. The C++ string Class
C++字符串类
-
The C++ string Class
C++ 使用 string 类处理字符串
string类中的函数
(1) 构造
(2) 追加
(3) 赋值
(4) 位置与清除
(5) 长度与容量
(6) 比较
(7) 子 串
(8) 搜索
(9) 运算符
-
注意事项
操作string对象中的字符串内容时,有时会用到“index”。
很多string的函数接受两个数字参数: index, n
(1) index: 从index号位置开始
(2) n: 之后的n个字符
-
Constructing a String (创建 string 对象)
Create an empty string using string’s no-arg constructor(用无参构造函数创建一个空字串):
string newString;
Create a string object from a string value or from an array of characters (由一个字符串常量或字符串数组创建string对象) :
string message{ "Aloha World!" };
char charArray[] = {'H', 'e', 'l', 'l', 'o', '\0'};
string message1{ charArray };
-
Appending a String (追加字符串)
You can use several overloade