Classes (1) Lecture 6
Abstract data types (ADTs) class Sales_item { public: Sales_item(const string &book); double avg_price( ) const; Sales_item& operator+= (const Sales_item &item); string isbn; unsigned int units_sold; double revenue; }; A class of objects whose logical behavior is defined by a set of values and a set of operations.
Abstract data types (ADTs) class Sales_item { public: Sales_item(const string &book); double avg_price( ) const; Sales_item& operator+= (const Sales_item &item); string isbn; unsigned int units_sold; double revenue; }; ADT not ADT struct Sales_data { string isbn; unsigned int units_sold; double revenue; };
Declaring C++ classes 类名 class或struct关键字 成员函数访问权限 成员函数声明 成员变量访问权限 class Sales_item { public: Sales_item(const string &book); double avg_price( ) const; Sales_item& operator+= (const Sales_item &item); string isbn; unsigned int units_sold; double revenue; }; 成员函数访问权限 成员函数声明 成员变量访问权限 成员变量声明 结尾分号
Member functions Definition double avg_price( ) const { return units_sold ? revenue / units_sold : 0; } Call Sales_item cpp_primer(“978-0-321-71411-4”); cout << cpp_primer.avg_price( );
Member functions 从调用语句cpp_primer.avg_price( )编程者知道是cpp_primer这一Sales_item类的对象调用了Sales_item类的成员函数avg_price( ),但在运行时,机器如何得知caller的信息? double avg_price( ) const { return units_sold ? revenue / units_sold : 0; } double Sales_item::avg_price(const Sales_item* const p) { return p->units_sold ? p->revenue / p->units_sold : 0; }
this pointer Member functions access the object on which they were called through an extra, implicit parameter named this. this is initialized with the address of the object on which the function was invoked. double avg_price( ) const { return units_sold ? revenue / units_sold : 0; } double avg_price( ) const { return this->units_sold ? this->revenue / this->units_sold : 0; }
const member functions this指针的类型是Sales_item* const,因为其指向的对象是当前调用函数的对象,该指向不可更改。 类成员函数参数列表后跟随的关键字const是进一步限定this指针的类型为const Sales_item* const,限定该成员函数不可更改任何成员变量。 double avg_price( ) const { return units_sold ? revenue / units_sold : 0; } double avg_price( ) const { return this->units_sold ? this->revenue / this->units_sold : 0; }
Separate compilation // 调用avg_price( )… class scope Sales_item.h #ifndef SALES_ITEM_H_ #define SALES_ITEM_H_ class Sales_item { public: double avg_price( ) const; // other code pieces }; #endif Sales_item.h #include “Sales_item.h” double Sales_item::avg_price( ) const { return units_sold ? revenue/units_sold : 0; } Sales_item.cpp // 调用avg_price( )… Main.cpp
Returning the caller object 实现一个成员函数,将一条Sales_item记录与当前记录合并:revenue和units_sold分别对应相加。 ??? combine(const Sales_item &item) { this->revenue += item.revenue; this->units_sold += item.units_sold; return ???; }
Returning the caller object void combine(const Sales_item &item) { this->revenue += item.revenue; this->units_sold += item.units_sold; } Which one is better? Sales_item& combine(const Sales_item &item) { this->revenue += item.revenue; this->units_sold += item.units_sold; return *this; } Version 1 Version 2
Returning the caller object 现有4个Sales_item,需要把它们合并起来。 Version 1 item1.combine(item2); item1.combine(item3); item1.combine(item4); combine的返回值是item1的引用 Version 2 item1.combine(item2).combine(item3).combine(item4);
Constructors 构造函数 构造函数用于定义如何创建/初始化一个对象。 构造函数的函数名与类名相同 构造函数没有返回值 一个类可以有多个重载的构造函数 构造函数不可以被声明为const 当且仅当程序员没有声明任何构造函数时,编译器会自动生成一个默认构造函数
Constructors 默认构造函数,各built-in成员变量采用默认的初始值 默认构造函数,显式指定初始值 构造函数初始化列表 Sales_item::Sales_item( ) { } Sales_item::Sales_item( ) = default; 默认构造函数,显式指定初始值 Sales_item::Sales_item( ): isbn(“”), revenue(0.0), units_sold(0) { } 构造函数初始化列表 Sales_item::Sales_item(const string &book): isbn(book), revenue(0.0), units_sold(0) { } Sales_item::Sales_item(const string &book, double r, unsigned int u): isbn(book), revenue(r), units_sold(u) { } 重载构造函数
Constructors 每个类只能有一个默认构造函数。 Why? 如果定义了非默认构造函数,那么编译器不会自动生成默认构造函数。 class Sales_item { public: Sales_item(const string &book); // other code pieces }; Sales_item cpp_primer; error: no matching constructor for initialization of 'Sales_item'
Constructor initializer list 必须使用构造函数初始化列表的情况:初始化常量成员 class SomeClass { public: SomeClass( ): N(0) { } private: const int N; }; class SomeClass { public: SomeClass( ) { N = 0; } private: const int N; }; ok error: cannot assign to non-static data member 'N' with const-qualified type 'const int'
Constructor initializer list 必须使用构造函数初始化列表的情况:初始化引用成员 class ClassA { public: ClassA(ClassB &v): obj(v) { } private: ClassB &obj; }; class ClassA { public: ClassA(ClassB &v) { obj = v; } private: ClassB &obj; }; ok error: constructor for 'ClassA' must explicitly initialize the reference member ‘obj'
Constructor initializer list 成员变量按照在类中声明的顺序被初始化。 好的编程习惯是按在类中声明的顺序写constructor initializer list。 class SomeClass { public: SomeClass( ): j(1), i(j) { } private: int i; int j; }; warning: field ‘j' will be initialized after field 'i'
Copy constructor 每个类可以有一个拷贝构造函数,用来定义类的对象如何被复制。 当一个类没有拷贝构造函数时,编译器自动生成一个拷贝构造函数。 Sales_item(const Sales_item &item): isbn(item.isbn), revenue(item.revenue), units_sold(item.units_sold) { }
Deep copy vs. shallow copy 编译器自动生成的拷贝构造函数只能实现浅拷贝。 class ClassA { public: ClassA(ClassB *p): ptr(p) { } private: ClassB *ptr; }; ptr ptr shallow copy
Deep copy vs. shallow copy class ClassA { public: ClassA(ClassB *p): ptr(p) { } ClassA(const ClassA &obj) { ptr = new ClassB(*obj.ptr); } private: ClassB *ptr; }; ptr ptr deep copy
Constructors 建议:不要依赖编译器自动生成的默认/拷贝构造函数。
Destructor 析构函数 当对象结束生命期时由系统自动调用,清理并释放对象所占用的内存。 析构函数的函数名为类名前加上~符号 析构函数没有返回值 一个类只能有1个析构函数 析构函数不可以被声明为const 当程序员没有声明析构函数时,编译器会自动生成一个默认析构函数 建议不要依赖编译器自动生成的析构函数
Destructor 析构函数 多数情况下这段代码可以正常执行、正常结束,但这段代码会造成内存泄漏(memory leak)。 class SomeClass { public: SomeClass(int m): n(m) { ptr = new int[m]; } private: int n; int *arr; }; 多数情况下这段代码可以正常执行、正常结束,但这段代码会造成内存泄漏(memory leak)。
Memory segments The memory is typically divided into a few different segments: Code segment: compiled program BSS segment: zero-initialized global & static variables Data segment: initialized global & static variables Heap: dynamically allocated variables Call stack: function parameters, local variables & other function-related information https://www.learncpp.com/cpp-tutorial/79-the-stack-and-the-heap/ [Nov 16, 2018] Image from https://en.wikipedia.org/wiki/Data_segment [Nov 16, 2018]
Dynamically allocating memory int a = 3; int *p = new int(3); 局部变量,在stack中 动态分配的变量,在heap中 到C++11标准为止,局部数组都要求在编译时确定数组大小,因此在声明/定义时其长度不可以是变量。 GCC提供编译器扩展,接受左侧的写法 size_t n = 7; int b[n]; int *q = new int[n]; 动态分配的数组,在heap中
Advice 学习时要注意区分哪些feature是C++ standard,哪些feature是compiler extension。
Dynamically allocating memory size_t n = 7; int *q = new int[n]; delete [ ] q; int *p = new int(3); delete p; 释放内存 如果程序员不释放,那么在程序结束后由操作系统回收,但程序在正常结束前可能已崩溃。 Heap的使用原则: 需要的时候申请分配,不需要的时候及时释放 通常,谁申请谁释放
Dynamically allocating memory 应判断内存分配是否成功 size_t n = 7; int *q = nullptr; if ( (q = new int[n]) == nullptr) { // 分配不成功,进行异常处理 } // 分配成功,执行后续程序 delete [ ] q; int *p = nullptr; if ( (p = new int(3)) == nullptr ) { // 分配不成功,进行异常处理 } // 分配成功,执行后续程序 delete p;
Destructor 析构函数 析构函数 class SomeClass { public: SomeClass(int m): n(m), ptr(nullptr) { if ( (ptr = new int[m]) == nullptr ) { // 分配失败,执行异常处理 } } ~SomeClass( ) { delete [ ] ptr; } private: int n; int *arr; }; 析构函数
Access control & encapsulation 封装 class Sales_item { public: Sales_item(const string &book); double avg_price( ) const; Sales_item& operator+= (const Sales_item &item); private: string isbn; unsigned int units_sold; double revenue; }; 可以被程序中的其他部分访问 只能被Sales_item的成员访问
Friends 某些情况下,类外部的函数需要访问类的私有数据成员。 换句话说,有些需要访问类的私有数据成员的函数不适合作为类的成员函数。 例:为Sales_item类定义重载的<<操作符,使得可以写cout << obj。 注意:操作符<<必须有两个参数ostream&和const Sales_item&,必须返回ostream&。
Friends 作为public成员函数 class Sales_item { public: ostream& operator<< (ostream &out, const Sales_item &item) { out << item.isbn << “, “ << item.revenue << “, “ << item.units_sold; return out; } }; error: overloaded 'operator<<' must be a binary operator (has 3 parameters)
Friends 作为类外部的函数 error: 'isbn' is a private member of 'Sales_item’ error: 'revenue' is a private member of 'Sales_item' error: 'units_sold' is a private member of 'Sales_item' class Sales_item { // blablabla }; ostream& operator<< (ostream &out, const Sales_item &item) { out << item.isbn << “, “ << item.revenue << “, “ << item.units_sold; return out; }
Friends 不是类的成员函数 参数中没有隐藏的this指针 可以访问类的私有成员 需要在类的内部声明 作为类的友元函数 可以在类的外部定义 作为类的友元函数 class Sales_item { public: friend ostream& operator<< (ostream &out, const Sales_item &item) { out << item.isbn << “, “ << item.revenue << “, “ << item.units_sold; return out; } };
Static members Classes sometimes need members that are associated with the class, rather than with individual objects of the class type. 例:假如有规定每本书的价格不得超过一个上限max_price class Sales_item { public: double max_price; // other code pieces }; 这样行不行?如果上限要更改,怎么办?
Static members max_price不是任何对象的一部分,所有的对象共享一个max_price。 class Sales_item { public: static double max_price; // other code pieces }; max_price不是任何对象的一部分,所有的对象共享一个max_price。
Static members error: undefined symbol ‘Sales_item::max_price’ class Sales_item { public: Sales_item( ) { max_price = 5.0; } static double max_price; // other code pieces }; error: undefined symbol ‘Sales_item::max_price’ max_price不是任何对象的一部分,所以不能通过构造函数初始化。
Static members class Sales_item { public: Sales_item( ) { } static double max_price = 5.0; // other code pieces }; error: non-const static data member must be initialized out of line 非常量的static成员必须在类外定义。
Static members class Sales_item { public: Sales_item( ) { } static double max_price; // other code pieces }; double Sales_item::max_price = 5.0; class Sales_item { public: Sales_item( ) { } static const double MAX_PRICE = 5.0; // other code pieces };
Using static members 不需要Sales_item类的对象 class Sales_item { public: static double max_price; // other code pieces }; double Sales_item::max_price = 5.0; Sales_item::max_price = 6.0; cout << Sales_item::max_price; 不需要Sales_item类的对象
Static member functions 静态成员函数可以使用静态成员变量/函数 静态成员函数不可以使用非静态成员变量/函数 class Sales_item { public: static void updateMaxPrice(double mp) { max_price = mp; } double avg_price( ) const { //… } bool over_priced( ) const { return avg_price( ) > max_price; } static double max_price; // other code pieces }; double Sales_item::max_price = 5.0; 非静态成员函数可以使用静态成员变量/函数
Using static member functions Sales_item item1(“001”, 100.0, 10); item2(“002”, 16.0, 8); cout << item1.over_priced( ) << “, “ << item2.over_priced( ) << endl; Sales_item::updateMaxPrice(12.0); 1, 0 0, 0
Next lecture C++ Primer, Chapters 13~15