Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~
关于数据抽象
核心思想
- separation of interface (operations that users of the class can execute) and implementation (成员、函数等),即encapsulation (封装):users of the class can use the interface but have no access to the implementation
封装的好处
- User code cannot inadvertently corrupt the state of an encapsulated object
- The implementation of an encapsulated class can change over time without requiring changes in user-level code
this
- 通过对象调用成员函数时会隐式传该对象的地址
total.isbn() // 等价于 Sales_data::isbn(&total) // 此时,this绑定total
- a const pointer to the nonconst version of the class type,所以 (by default) we cannot bind this to a const object,也就是说不能直接通过常对象调用其成员函数
常成员函数
std::string isbn() const { return bookNo; }
- 参数 list 后的 const 表示:常成员函数中的 this 是 const T *const 型,即 this 指向常量
- isbn函数may read but not write to 成员变量
- 常变量、指向常变量的指针\引用只能调用常成员函数
关于作用域
- 一个类是一个作用域
- 定义在类外的成员函数,其名字前要加类名
Sales_data& Sales_data::combine(const Sales_data &rhs) { ... }
关于编译顺序
- 先编译 member declarations,再编译 member function bodies
- 所以 member function bodies may use other members of their class regardless of where in the class those members appear
I/O相关
栗子
istream &read(istream &is, Sales_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream &print(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
注意
- IO classes are types that cannot be copied, so we may only pass them by reference
Constructors
特点
- 没有return type
- 一个类可以有多个constructors,类似于重载
- 与类同名
- 不能被声明为const:定义一个常对象时,the object does not assume its “constness” until after the constructor completes the object’s initialization,因此,constructors can write to const objects during their construction
默认构造函数
class testClass
{
public:
testClass(); /* 默认构造函数1 */
testClass(int a, char b); /* 构造函数 */
testClass(int a=10,char b='c'); /* 默认构造函数2 */
private:
int m_a;
char m_b;
};
- 默认构造函数有两种:无参或所有参数都有默认值
- 一个类只能有一个默认构造函数,所以上段代码中的1和2不能共存
- 无论有没有其他构造函数,最好要自己定义一个默认构造函数,原因如下:
- 若定义了其他构造函数,则编译器不会定义synthesized default constructor,那么该类就没有默认构造函数了,此时 testClass A; 这句话会报错,因为必须提供两个参数a和b
- synthesized default constructor给的初始值可能是不合适的, 对于内置类型或复合类型的变量,可能会初始化为未定义
- sometimes the compiler is unable to synthesize one. For example, if a class has a member that has a class type, and that class doesn’t have a default constructor, then the compiler can’t initialize that member.
synthesized default constructor
- 若程序员未定义任何构造函数,编译器会定义构造函数:compiler-generated constructor is known as the synthesized default constructor
- 没有参数
- 初始化步骤
- 若有 in-class initializer(即类中有 int x = 0之类的),则使用它
- 否则,用 default-initialize
in-class initializer
- = 型:int x = 0;
- {} 型:std::vector
screens{Screen(24, 80, ' ') };
= default
Sales_data() = default;
- = default 的含义是让编译器generate synthesized default constructor
只有一个参数的constructor
- 可以把参数隐式转换为类类型,比如下面这段代码中:the compiler automatically creates a Sales_data object from the given string. That newly generated (temporary) Sales_data is passed to combine
class Sales_data {
public:
// defines the default constructor as well as one that takes a string argument
Sales_data(std::string s = ""): bookNo(s) { }
Sales_data &combine(const Sales_data& s) { ... }
}
string null_book = "9-999-99999-9";
// constructs a temporary Sales_data object with units_sold and revenue equal to 0 and bookNo equal to null_book
item.combine(null_book);
- 只允许隐式转换一次
// error: requires two user-defined conversions:
// (1) convert "9-999-99999-9" to string
// (2) convert that (temporary) string to Sales_data
item.combine("9-999-99999-9");
// ok: explicit conversion to string, implicit conversion to Sales_data
item.combine(string("9-999-99999-9"));
// ok: implicit conversion to string, explicit conversion to Sales_data
item.combine(Sales_data("9-999-99999-9"));
item.combine(static_cast("9-999-99999-9"));
- 特殊情形:用输入构造对象
Sales_data::Sales_data(std::istream&) { ... };
item.combine(cin);
- 禁止隐式转换:explicit(这个关键字只对含单个参数的constructor有用,且只能出现在class中)
class Sales_data {
public:
Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { }
explicit Sales_data(const std::string &s): bookNo(s) { }
explicit Sales_data(std::istream&);
};
// 下面的编译不通过
item.combine(null_book); // error: string constructor is explicit
item.combine(cin); // error: istream constructor is explicit
// error: explicit allowed only on a constructor declaration in a class header
explicit Sales_data::Sales_data(istream& is) { read(is, *this); }
- explicit的另一个性质:explicit Constructors Can Be Used Only for Direct Initialization
Sales_data item1 (null_book); // ok: direct initialization
// error: cannot use the copy form of initialization with an explicit constructor
Sales_data item2 = null_book;
- 使用explicit constructor的library class
vector v(n); // 使用了explicit constructor
Constructor Initializer List
Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { }
- 若只提供了s,则units_sold和revenue会被synthesized default constructor初始化
- 即使没有Constructor Initializer List,the members of this object are still initialized before the constructor body is executed
- Members that do not appear in the constructor initializer list are initialized by the corresponding in-class initializer (if there is one) or are default initialized
- 执行完Constructor Initializer List才会执行函数体
用Constructor Initializer List初始化 与 赋值
- 此为Constructor Initializer List初始化
Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { }
- 此为赋值
Sales_data::Sales_data(const string &s, unsigned cnt, double price)
{
bookNo = s;
units_sold = n;
revenue = n * p;
}
- Constructor Initializer List初始化与赋值的不同程度取决于the type of the data member
- 必须Constructor Initializer List初始化的情形:① const成员 ② 引用成员 ③ 没有默认构造函数的类的对象
class ConstRef {
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
// error: ci and ri must be initialized
ConstRef::ConstRef(int ii)
{ // assignments:
i = ii; // ok
ci = ii; // error: cannot assign to a const
ri = i; // error: ri was never initialized
}
// ok: explicitly initialize reference and const members
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) { }
初始化顺序
- Members are initialized in the order in which they appear in the class definition
- 与Constructor Initializer List无关
class X {
int i;
int j;
public:
// undefined: i is initialized before j
X(int val): j(val), i(j) { }
};
Delegating Constructors
- A delegating constructor uses another constructor from its own class to perform its initialization
- 栗子
class Sales_data {
public:
// nondelegating constructor initializes members from corresponding arguments
Sales_data(std::string s, unsigned cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt*price) { }
// remaining constructors all delegate to another constructor
Sales_data(): Sales_data("", 0, 0) {}
Sales_data(std::string s): Sales_data(s, 0,0) {}
Sales_data(std::istream &is): Sales_data() { read(is, *this); }
// ...
};
Copy, Assignment, and Destruction
synthesized
- 若未定义这些操作,编译器会synthesize them for us
- 编译器生成的操作 execute by copying, assigning, or destroying each member of the object
- the synthesized versions are unlikely to work correctly for classes that allocate resources that reside outside the class objects themselves:比如,管理动态内存的类
Access Specifiers
public
- accessible to all parts of the program
- 定义了类的接口
private
- accessible to the member functions of the class
- 封装了类的实现
struct与class
- struct 默认是 public
- class 默认是 private
Friend
定义及性质
- 一个类的友元函数/类可以access its nonpublic members
- friendship没有传递性和交换性
- 友元的声明只能在类内,但可以在类内任何位置,效果等价
- 在friend声明语句中(即开头是friend的语句)可以使用未声明的类和非成员函数
- 在friend声明语句中出现的name(不含友元函数/类本身)被视为对该友元所在scope可见,友元函数/类本身(哪怕在声明的同时定义了,也)被视为不可见
struct X {
friend void f() { /* friend function can be defined in the class body */ }
X() { f(); } // error: no declaration for f
void g();
void h();
};
void X::g() { return f(); } // error: f hasn't been declared
void f(); // declares the function defined inside X
void X::h() { return f(); } // ok: declaration for f is now in scope
友元函数
- 友元函数的定义:在类中加一句该函数的声明,并以friend开头(友元函数声明可以出现在类内的任何位置)
class Sales_data { // friend declarations for nonmember Sales_data operations added friend Sales_data add(const Sales_data&, const Sales_data&); friend std::istream &read(std::istream&, Sales_data&); friend std::ostream &print(std::ostream&, const Sales_data&); // other members and access specifiers as before public: ...
- 类中对友元函数的声明并不是general function declaration,friend functions must be declared/defined outside the class before they can be used(不过有的编译器并不要求这样)
- 可以在类内定义友元函数,自动inline
友元类
class Screen {
// Window_mgr members can access the private parts of class Screen
friend class Window_mgr;
// ...
};
友元成员函数
class Screen {
// Window_mgr::clear must have been declared before class Screen
friend void Window_mgr::clear(ScreenIndex);
// ...
};
- 上段代码要求按顺序完成如下步骤:① define the Window_mgr class, which declares, but cannot define, clear(因为Screen must be declared before clear can use the members of Screen)② define class Screen, including a friend declaration for clear ③ define clear, which can now refer to the members in Screen
类中的type definition
栗子
class Screen {
public:
typedef std::string::size_type pos;
// alternative way to declare a type member using a type alias
using pos = std::string::size_type;
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
注意
- unlike ordinary members, members that define types must appear before they are used
如何把成员函数变为inline
- member functions defined inside the class are automatically inline
- 在类中的声明前加上inline
class Screen { public: char get() const { return contents[cursor]; } // implicitly inline inline char get(pos ht, pos wd) const; // explicitly inline Screen &move(pos r, pos c); // can be made inline later ... }
- 在类外的定义前加上inline
inline // we can specify inline on the definition Screen &Screen::move(pos r, pos c) { pos row = r * width; // compute the row location cursor = row + c ; // move cursor to the column within that row return *this; // return this object as an lvalue }
mutable Data Members
- 即使在 const 对象中,mutable data members 也不是 const 的
- 任何成员函数,包括 const 成员函数都 may change a mutable member
- 定义格式:mutable T variable_name
return *this;
返回类型为引用 vs 为非引用
myScreen.move(4,0).set('#');
// 若move的返回类型是Screen&,则等价于
myScreen.move(4,0); // return 当前对象的引用,即 return 的是左值,not a copy of the object
myScreen.set('#');
// 若move的返回类型是Screen,则等价于
Screen temp = myScreen.move(4,0); // the return value would be copied
temp.set('#'); // the contents inside myScreen would be unchanged
关于const
- A const member function that returns *this as a reference should have a return type that is a reference to const
- 常对象只能调用常函数;非常对象可以调用常函数和非常函数,但有重载时,非常函数是一个更好的匹配
类对象的定义
Sales_data item1; // default-initialized object of type Sales_data
class Sales_data item1; // equivalent declaration
- 第二行是从c继承过来的
类的声明
class Screen;
- 声明后,it’s known that Screen is a class type but not known what members that type contains
- 只声明而未定义的类类型只能用于:define pointers or references to such types;declare (but not define) functions that use it as a parameter or return type
- 只有在定义了类之后,才能定义该类的对象,因为编译器需要知道该对象需要多大的存储空间
- 类的data members只能是定义了的类类型(有一个例外:)
- 因为在类的body complete前该类都是未定义的,所以a class cannot have data members of its own type
- 只要类名出现,就认为该类已声明,因此a class can have data members that are pointers or references to its own type
- member function bodies中可以用any name defined inside the class(因为函数体最后编译),但函数的parameter list和return type中只能使用在该函数之前出现的name,栗子:
typedef double Money;
string bal;
class Account {
public:
Money balance() { return bal; } // 这里的Money是double,bal是Money
typedef int Money; // error:不能再定义Money了
typedef double Money; // error:哪怕是相同的double也不可
private:
Money bal;
};
- 一般来说,an inner scope can redefine a name from an outer scope,但如果该name是一个type,则类不可以redefine它
类的作用域
- 对于在类外定义的成员函数:parameter list and the function body中可以直接出现类中定义的类型;返回类型中要用类中定义的类型,则必须加上“类名::”【Because the return type appears before the name of the class is seen, it appears outside the scope of class Window_mgr】
class Window_mgr {
public:
ScreenIndex addScreen(const Screen&, ScreenIndex);
...
};
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s, ScreenIndex i) {
...
}
类的编译过程
步骤
- 第一步:编译member declarations
- 第二步:Function bodies are compiled only after the entire class has been seen
成员函数的找名过程
对于成员函数中使用的name,找名过程如下:
- 第一步:look for a declaration of the name inside the member function and precede the use of the name
- 第二步:look for a declaration inside the class. All the members of the class are considered
- 第三步:look for a declaration that is in scope before the member function definition
int height; // defines a name subsequently used inside Screen
class Screen {
public:
typedef std::string::size_type pos;
void dummy_fcn(pos);
private:
pos cursor = 0;
pos height = 0, width = 0;
};
void Screen::dummy_fcn(pos height) {
cursor = width * height; // the parameter height
cursor = width * this->height; // member height
cursor = width * Screen::height; // member height
cursor = width * ::height;// the global height
}
定义对象vs定义函数
定义函数
Sales_data obj(); // ok: but defines a function, not an object
定义对象
Sales_data obj; // ok: obj is a default-initialized object
Aggregate Classes(聚合类)
定义
满足如下条件的是aggregate class:
- 所有data members都是public
- 没有定义任何constructor
- 没有in-class initializer
- 没有base classes
- 没有virtual functions
栗子
struct Data {
int ival;
string s;
};
初始化
- 格式
Data val1 = { 0, "Anna" };
- if the list of initializers has fewer elements than the class has members, the trailing members are value initialized
Literal Classes
定义
-
data members are all of literal type的聚合类是literal class;满足如下条件的非聚合类是literal class:
- data members all must have literal type
- 至少有一个constexpr constructor
- in-class initializer for built-in type必须是constant expression,in-class initializer for class必须使用那个class的constexpr constructor
- must use default definition for its destructor
特性
- literal class的constructor可以是constexpr function
- constexpr constructor的要求:函数体必须为空、必须initialize every data member、initializers must either use a constexpr constructor or be a constant expression
- constexpr constructor的作用:generate objects that are constexpr、generate parameters or return types in constexpr functions
- Literal Classes可以包含非constexpr的函数,也可以作为普通类型使用,但作为constexpr常量时,只能使用其constexpr的构造函数和成员函数
- Literal Classes可以让constexpr常量在编译时初始化,参与编译时的优化,而不是像static那样推迟到程序启动时,这使得constexpr常量对象可以被放在程序的资源中
栗子
class Debug {
public:
constexpr Debug(bool b = true): hw(b), io(b), other(b) { }
constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) { }
constexpr bool any() { return hw || io || other; }
void set_io(bool b) { io = b; }
void set_hw(bool b) { hw = b; }
void set_other(bool b) { hw = b; }
private:
bool hw; // hardware errors other than IO errors
bool io; // IO errors
bool other; // other errors
};
静态成员
static的含义
- 静态成员associated with class, not object
声明静态成员
- 栗子:each Account object will contain two data members—owner and amount. There is only one interestRate object that will be shared by all the Account objects.
class Account {
public:
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
静态成员函数
- not bound to any object,所以没有this指针,函数体内无法使用this,也不能call a nonstatic member(因为需要implicitly使用this)
- 不能be declared as const(const是表明:函数不会修改该函数访问的目标对象的数据成员,而静态成员函数只能访问其参数、静态数据成员和全局变量,这些数据都不是对象状态的一部分,所以肯定不会修改“目标对象的数据成员”,没必要用const)
使用静态成员
- 用scope operator
r = Account::rate();
- 用该类的object/reference/pointer
Account ac1;
Account *ac2 = &ac1;
// equivalent ways to call the static member rate function
r = ac1.rate(); // through an Account object or reference
r = ac2->rate(); // through a pointer to an Account object
- 成员函数可以直接使用静态成员
class Account {
public:
void calculate() { amount += amount * interestRate; }
private:
static double interestRate;
};
定义静态成员
- 在类外定义静态成员函数时,不需要再写static关键字
void Account::rate(double newRate)
{
interestRate = newRate;
}
静态成员的初始化
- not defined when we create objects of the class,因此也not initialized by the class’ constructors
- we must define and initialize each static data member outside the class body & outside any functions(因此生存期until the program completes)【除了下面的特殊情况:“静态数据成员的In-Class Initialization”】
- a static data member may be defined only once
double Account::interestRate = initRate();
静态数据成员的In-Class Initialization
- 对const integral类型的静态数据成员,可以in-class initialize(但最好是在类外初始化)
- 对constexprs of literal type类型的静态数据成员,必须in-class initialize,且initializer必须是constant expression
- 若const或constexpr静态数据成员只在contexts where the compiler can substitute the member’s value中使用(比如下面这段代码),则可以不在类外定义它;否则(比如要pass Account::period to a function that takes a const int&),必须在类外定义它,而且如果它有In-Class Initialization,那么类外的定义不能给它初始值
class Account {
public:
static double rate() { return interestRate; }
static void rate(double);
private:
static constexpr int period = 30;// period is a constant expression
double daily_tbl[period];
};
// 类外定义
constexpr int Account::period; // initializer provided in the class definition
静态成员的特殊性质
- 静态数据成员可以是已声明而未定义的类型,比如,其类型可以是该类本身
class Bar {
public:
// ...
private:
static Bar mem1; // ok: static member can have incomplete type
Bar *mem2; // ok: pointer member can have incomplete type
Bar mem3; // error: data members must have complete type
};
- we can use a static member as a default argument
PS:使用非静态数据成员做为默认参数是错误的,因为这时没有一个具体的对象,无法从中得到成员的值
class Screen {
public:
Screen& clear(char = bkground);
private:
static const char bkground;
};