C++ primer 第七章-类

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不能共存
  • 无论有没有其他构造函数,最好要自己定义一个默认构造函数,原因如下:
    1. 若定义了其他构造函数,则编译器不会定义synthesized default constructor,那么该类就没有默认构造函数了,此时 testClass A; 这句话会报错,因为必须提供两个参数a和b
    2. synthesized default constructor给的初始值可能是不合适的, 对于内置类型或复合类型的变量,可能会初始化为未定义
    3. 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
  • 没有参数
  • 初始化步骤
    1. 若有 in-class initializer(即类中有 int x = 0之类的),则使用它
    2. 否则,用 default-initialize

in-class initializer

  • = 型:int x = 0;
  • {} 型:std::vector<Screen> 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<Sales_data>("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<T> 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;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342