tag 标签: 类

相关博文
  • 热度 13
    2016-3-24 20:42
    1595 次阅读|
    1 个评论
    7.4 Class Scope ---------------------------------------------------------------- void Window_mgr::clear(ScreenIndex i) {     Screen s = screens ;     s.contents = string(s.height * s.width, ' '); } 此函数中的Window_mgr,说明这个函数都在它的scope里。 如果函数的返回值要使用Window_mgr中的类型的话,必须加上Window_mgr的specify。 比如: Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen s); 编译器只有在找到所以类的成员声明之后,才会去处理成员函数的定义。 编译器会现在class里面找,然后在enclosing scope里找,再找不到就会报错。 在类的内部和外部,变量可以重名,但是typedef的定义不能够重名,否则就会报错。 编译器如何处理成员函数内部的名称?  • 在成员函数内部寻找,且仅仅在使用这个名称之前的代码。  • 在类里面找。  • 在这个类前面的scope中找。 如果我们想使用类成员的名称,而不是成员函数里的本地名称,可以通过this或者类名访问: this-height; Screen::height; 如果我们想使用类外面的全局名称,就使用"::"这个符号,它意味着全局名称: ::height 7.5 Constructors Revisited ---------------------------------------------------------------- 如果在定义一个变量时,是先定义再赋值,那么这个变量会先被赋为默认值, 因此不如在初始化的时候顺便把值给初始化了: string foo = "Hello World!"; 构造函数也是如此,与其在内部赋值,不如在它的参数列表里面指定其成员的初始值。 const类型的变量/引用/指针,以及没有默认构造函数的类,必须在参数列表里被初始化。 在构造函数的参数列表里,成员初始化的顺便并不是列表的顺序,而是它们在类中的存储顺序。 因此,最好按照类中的存储顺序来初始化成员变量,并且不要使用成员变量来初始化另一个成员变量。 Sales_data(std::string s = ""): bookNo(s) { } Sales_data(std::string s, unsigned cnt, double rev):     bookNo(s), units_sold(cnt), revenue(rev*cnt) { } Sales_data(std::istream is) { read(is, *this); } 第一个函数是默认的构造函数,因为它提供的参数列表和默认的参数列表并没有区别。 我们也可以使用空列表来调用它。 ---------------- Delegating Constructors 使用其他构造函数来形成自己的构造函数: Sales_data(std::string s, unsigned cnt, double price):     bookNo(s), units_sold(cnt), revenue(cnt*price) {} 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);} 此时,被delegating的构造函数的参数列表及函数体都会被执行,它们执行完了之后, delegating函数的函数体会接着执行。 ---------------- The Role of the Default Constructor 什么时候会用到默认的构造函数?  • 在block scope中定义nonstatic变量,且没有初始化它。  • 类包含有synthesize default constructor的成员类。  • 当构造函数没有显式的初始化每一个成员变量时。  • 当没有提供完整的数组初始化个数时。  • 当定义一个本地的static对象且没有初始化时。  • 当初始化类似vector的变量时,T()使用了类作为其成员。 ---------------- Implicit Class-Type Conversions 不仅仅是build-in类型可以进行转换,class类型也可以。 单参数的构造函数,就代表了一个类型转换,因此这类函数也称为converting constructors。 但要注意的是,转换函数只能接受一次转换,如果构造函数接收string作为参数,那就不能使用: combine("9-999-99999"); 如果在构造函数前加上explicit关键字,意味着我们不能使用它来行类型的隐式转换。 explicit只需要用在单参数的converting constructors上; explicit只能用在类的内部,不能在类的外部使用; explicit只能用在直接的initialization上: Sales_data item1(null_book);   // OK Sales_data item1 = null_book;  // ERROR ---------------- Aggregate Classes 我们可以直接访问它的成员: struct Data {     int ival;     string s; }; Data val1 = {0, "Anna"}; 这种直接访问类成员的方式看其来很简单,实际上有很多缺点:  • 所有的成员变量必须是public。  • 类的使用者必须负责进行冗长且乏味的赋值操作。  • 一旦类修改,所有的赋值操作都要修改。 ---------------- Literal Classes 一个aggregate class,如果它的成员变量都是literal类型的,就可以称它literal class。 一个非aggregate class,满足如下要求的也是literal class:  • 所有的成员都是literal类型;  • 至少有一个constexpr构造函数;  • in-class的初始化必须是constant表达式,或者成员类有constexpr构造函数;  • 必须使用默认的析构函数。 7.6 Class Members ---------------------------------------------------------------- static关键字,使得类的成员不再属于某个单独的类对象,而是属于整个类。 被static修饰的成员,可以是const/引用/数组/clas或其他。 被static修饰的成员,可以是private或者public。 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(); }; 每个Account个体仅仅包含owner和amount两个成员,其他被static修饰的都是共用的。 被static修饰的函数,不会有this指针。 被static修饰的函数,也不会是const的。 我们可以通过类的类型名直接访问static变量或函数: double r; r = Accout::rate(); 当然我们仍然可以通过具体的对象来访问它们。 成员函数可以直接访问被static修饰的成员变量。 在类的外部定义static函数时,不需要使用static修饰,在类的内部做就可以了。 类的构造函数不会去处理static成员变量,同理我们不能在类的内部去处理static变量。 static变量必须在类的外部进行定义和处理,就像是全局变量一样。 为了保证static成员变量只被定义一次,我们通常将它们放置在对应的源文件中。 一般来说,static成员变量不能在类的内部进行初始化,但也有例外的情况: 如果static成员是const integral类型,就可以在内部进行。 如果static成员只用在了编译器可直接使用它的值的地方,就无需在外面再定义,否则就需要。 如果static成员进行了类内部的初始化,外部的定义就不能再赋值了。 static成员定义在了类外面,因此它的数据类型可以是这个类本身。 static成员可以作为构造函数的参数。
  • 热度 13
    2016-3-24 20:40
    836 次阅读|
    1 个评论
    为什么《C++ Primer》是一本经典的C++书籍? 因为直到第7章才讲到了类的概念。 之前的那么多准备工作,并不是浪费时间,而是透彻的讲述了C++的特点,它们是构建C++的基石。 所以不要跳过它们,而是要踏踏实实的学习前6章。 现在,才到了解类的时候。 7.1 Defining Abstract Data Types ---------------------------------------------------------------- 对于类来说,interface和implementation一般是分开的。 使用类的人,只关心interface; 编写类的人,才负责implementation。 一般来说,类的抽象封装了类的数据,对外可见的只有interface。 虽然设计类和使用类的很可能是同一个人,但还是要把它们的角色定位分开。 设计类的接口的时候,应该使得类容易使用;而使用类的时候,应该了解接口的特点。 举个例子: struct Sales_data {     string isbn() const {return bookNo;}     Sales_data combine(const Sales_data);     double avg_price() const;     string bookNo;     unsigned units_sold;     double revenue; }; Sales_data add(const Sales_data, const Sales_data); ostream print(ostream, const Sales_data); istream read(istream, Sales_data); double Sales_data::avg_price() const {     if (units_sold)         return revenue / units_sold;     else         return 0; } Sales_data Sales_data::combine(const Sales_data rhs) {     units_sold += rhs.units_sold;     revenue += rhs.revenue;     return *this; } ---------------- 函数里可以定义在类的内部,比如isbn()函数。 isbn()函数返回的是调用它的对象里的bookNo,因为参数默认会使用this指针,也就是对象本身。 this是一个const类型的指针,它不可被修改。 为什么isbn()函数后面要添加const关键字? 因为this的类型是Sales_data *const,它意味着我们不能使用this来绑定一个const的对象。 鉴于isbn()不需要修改Sales_data的成员,使用const告诉编译器,this指向了不能被修改的const对象。 isbn()也称之为const成员函数。 另外,如果对象(或其引用/指针)是const的,那么它只能通过const成员函数访问。 编译类分两步,先是声明,然后是定义,因此类里面的变量可以在类的任何位置被使用。 类的成员函数可以定义在类的外面,比如avg_price()。它必须要加上类的名字,并且保留const。 类的返回值可以是*this,比如combine()。如果要定义具有操作符功能的函数,那么它应该表现得像操作符。 因此combine()函数返回了类对象的引用,作为lvalue。 再举个例子: struct Contacts {     string name;     string address;     string tel;     string get_name() const {return name;}     string get_address() const {return address;}     string get_tel() const {return tel;} }; ---------------- 非类成员的相关函数 add/print/read函数虽然不是类的成员函数,但是它们却是接口。 它们和其他普通函数一样,通常在源文件中定义,在头文件中声明。且它们一般和类放置在一起。 IO类不能被复制,所以这里使用了引用。 IO类会随着读写而改变,因此这里也不能使用const来修饰它。 ---------------- Constructors 构造函数和类有着相同的名字,并且它没有返回值。 类可以有多个构造函数,因为函数可以重载,它们只能通过参数来区分。 构造函数不能声明为const。当成员变量中有const变量时,也可以在构造函数里被修改。 如果类不提供构造函数,那么它的成员变量会被默认的构造出来。 默认的构造函数称之为default constructor,它没有参数; 编译器提供的默认的构造函数称之为synthesized default constructor。 如果成员变量有默认值,那么默认的构造函数就会使用它们。 如果我们定义了任何构造函数,那么编译器都不会再生成默认的构造函数了,除非我们自己再定义它。 为什么需要自己的构造函数呢?  • 编译器会生成默认的构造函数,仅仅是在没有定义任何构造函数的情况下。  • 默认的构造函数可能并不能正确的初始化成员变量,比如指针和引用。  • 编译器无法生成默认的构造函数,比如成员变量中有不能被默认构造的类时。 在Sales_data例中,怎么编写自己的构造函数?(根据实际情况的四种途径)  • istream,读取输入  • const string表示isbn,unsigned表示卖出本数,double表示价格  • const string表示isbn,其它采用默认  • 空参数列表(默认构造函数) 仍然是Sales_data的例子: struct Sales_data {     Sales_data() = default;     Sales_data(const string s): bookNo(s) {}     Sales_data(const string s, unsigned n, double p):         bookNo(s), units_sold(n), revenue(p*n) {}     Sales_data(istream);     string isbn() const {return bookNo;}     Sales_data combine(const Sales_data);     double avg_price() const;     string bookNo;     unsigned units_sold;     double revenue; }; Sales_data() = default; 我们提供这个构造函数,仅仅是因为我们想使用其他的构造函数。 这个构造函数做了synthesized version的事情,因此我们不需要编写它。 = default关键字告诉编译器替我们做这件事情。 = default关键字可以出现在类内部,也可以放置在外部。 = default关键字在内部,说明函数inline;在外部,默认不是inline。 Sales_data(const string s): bookNo(s) {} Sales_data(const string s, unsigned n, double p):     bookNo(s), units_sold(n), revenue(p*n) {} 这两个构造函数,都是通过Initializer List来实现初始化。 这里没有指定值的变量,会根据synthesized default的步骤进行初始化。 构造函数不应该覆盖in-class初始化,除非使用不同的值;不能使用in-class时,再显式的提供。 Sales_data(istream); 这个函数的定义在类的外部,它会从istream读取数据来初始化Sales_data。 值得注意的是,在函数体执行之前变量会先被in-class初始化,然后再由istream初始化。 再举个例子: struct Contacts {     Contacts(const string name, const string address, const string tel):         name(name), address(address), tel(tel) {}     string name;     string address;     string tel;     string get_name() const {return name;}     string get_address() const {return address;}     string get_tel() const {return tel;} }; ---------------- 类的复制、赋值和析构 如果我们不定义这些操作,编译器会替我们做这些工作。 但是编译器替我们做的这些systhesized工作,不一定会正确的运行,尤其申请对象外的资源的时候。 动态内存的管理,一般不能依赖sysnthesized versions。 However,需要动态内存管理的类,一般会使用vector或者string来管理必要的存储, 因为vector和string的复制、赋值和析构操作可以保证是安全的。 除非我们知道如何定义copy操作,否则动态申请的资源应该直接存储在类的成员里。 7.2 Access Control and Encapsulation ---------------------------------------------------------------- 目前定义的类,任何人都可以对它的成员变量进行操作,都可以调用它的任何函数。 要控制用户对它的访问,需要对它进行封装。 public:可以在程序的任何位置访问它。 private:仅仅能够被类的成员函数访问。 比如上面的Sales_data类: struct Sales_data { public:     Sales_data() = default;     Sales_data(const string s): bookNo(s) {}     Sales_data(const string s, unsigned n, double p):         bookNo(s), units_sold(n), revenue(p*n) {}     Sales_data(istream);     string isbn() const {return bookNo;}     Sales_data combine(const Sales_data); private:     double avg_price() const;     string bookNo;     unsigned units_sold;     double revenue; }; public和private可以多次出现,它们的作用域是直到下一个specifier为止。 既然已经学到了封装,我们再做一个更精细的更改,将struct更改为class。 这仅仅是格式的变化,因为使用它们中的任何一个都是可以的。 它们的区别仅仅是,class默认的访问控制是private,而struct默认的访问控制是public。 作为通用的编程习惯,如果我们定义一个所有成员皆可访问的class,那就使用struct; 如果其中有private成员,那就使用class。 ---------------- Friends 不属于类成员的interface函数,无法访问类的private变量,即使它需要访问。 我们可以将这些函数(或类)设置为Sales_data的friend,这样它们就能访问私有成员了。 方法是添加带有friend关键字的声明: struct Sales_data { friend istream read(istream is, Sales_data item); friend ostream print(ostream os, const Sales_data item); public:     Sales_data() = default;     Sales_data(const string s): bookNo(s) {}     Sales_data(const string s, unsigned n, double p):         bookNo(s), units_sold(n), revenue(p*n) {}     Sales_data(istream);     string isbn() const {return bookNo;}     Sales_data combine(const Sales_data); private:     double avg_price() const;     string bookNo;     unsigned units_sold;     double revenue; }; friend关键字可以出现在类的任何地方,它不受访问控制的影响。 Friends并不是类的成员。 friend关键字告诉类谁可以访问它的private成员,但它并不能替代声明。 7.3 Additional Class Features ---------------------------------------------------------------- 上面所述的Sales_data类非常简单,friend的特点也非常简单。 类还包含很多上述未涉及到的特征。 我们首先定义两个会互相访问的类Screen和Window_mgr: class Screen { public:     typedef string::size_type pos;     Screen() = default;     Screen(pos ht, pos wd, char c):         height(ht), width(wd), contents(ht * wd, c) {}     char get() const {return contents ;}     inline char get(pos ht, pos wd) const;     Screen move(pos r, pos c);     Screen display() const; private:     pos cursor= 0;     pos height = 0, width = 0;     string contents;     mutable size_t access_ctr; }; class Window_mgr { private:     vectorScreen screens{Screen(24, 80, ' ')}; }; 这里使用using也是一样的效果。 另外,typedef必须要先声明,才能被使用,所以这里将它放置在了类的起始位置。 定义在类内部的函数,默认为inline函数。 inline关键字也可以用在类外部的成员函数定义上,但是它们得定义在header文件内。 类的成员函数也可以被重载,规则和非成员函数是一样的。 如果我们希望永远有能力更改某一个成员变量,即使在const成员函数里面, 可以将这个成员变量添加mutable关键字。 一个被mutable修饰的变量绝不会是const的,即使类object是const的。 当我们初始化一个class成员变量时,是在为它的构造函数提供参数,比如: vectorScreen screens{Screen(24, 80, ' ')}; ---------------- return *this 这个return语句说明成员函数返回的是调用它的对象,不是副本,而是对象本身。 这个return语句返回的是lvalue,意味着它可以被赋值,因此可以有: myScreen.move(4, 0).set('#'); 需要说明的是,如果返回的是Screen或者Screen的引用,它们的表现是完全不一样的。 返回的是Screen的话,对象就会被复制一份副本,操作的就不是原来这个对象了。 如果成员函数是const的,比如display(),意味着它的返回值也是const的,即*this是const的, 这样就不能连锁使用了。 为了解决这个问题,我们可以定义两个版本的函数,通过重载实现const和nonconst的返回: public:     Screen display(ostream os) {do_display(os); return *this;}     const Screen display(ostream os) const         {do_display(os); return *this;} private:     void do_display(ostream os) const {os contents;} 这里推荐将公共的部分放置在私有成员中。 原因是:  • 避免在不同的地方编写两份同样的代码。  • 公共部分的函数,有可能在未来变得更加复杂。  • 公共部分的函数,有可能会增加调试代码。  • 公共部分的函数,定义在类的内部,默认是inline的,不会带来额外的资源开销。 ---------------- class types 两个不同名称的类,即使成员完全一样,也是不同的类型,不能相互赋值。 以下两种方式都正确: Sales_data item1; class Sales_data item1; 类和函数一样,可以在定义前先声明。 我们可以为这样的类声明定义指针或引用,也可以将它们作为函数声明(非定义)的参数和返回值。 类必须先定义,才能使用它的成员,因此类不能有它自身做其成员的情况,但可以是引用或指针。 ---------------- Friendship Revisited 除了函数可以作为类的友元外,其他的类或类的成员函数也可以作为类的友元: class Screen {     friend class Window_mgr;     /* ...... */ }; 或者只声明其中的某个函数是友元: class Screen {     friend void Window_mgr::clear(ScreenIndex);     /* ...... */ }; 声明类的成员函数为友元时,需要仔细处理声明和定义之间的关系。 比如Window_mgr和Screen在代码中必须以这样的顺序定义或声明:  • 定义Window_mgr类,声明clear但不能定义它。  • 定义Screen类,包括对clear友元的声明。  • 最后定义clear函数。 如果B和C都是A的友元,不意味着B和C是友元。 如果函数是可重载的,那么声明友元的时候,比如声明具体的那个。 类、类成员函数作为友元时,友元的声明可以在它们本身的声明之前。 但即使是类成员函数作为友元,它也必须在类外面被声明一次。
相关资源
  • 所需E币: 4
    时间: 2019-12-25 16:17
    大小: 1.32MB
    上传者: 2iot
    C#完全手册……
  • 所需E币: 4
    时间: 2020-1-3 18:59
    大小: 122.59KB
    上传者: 16245458_qq.com
    从两个方面描述了基于DSP芯片TMS320DM642的视频采集驱动程序:在C64x系列DSP的实时操作系统DSP/BIOS上,使用类/微驱动模型的程序框架构建;基于EDMA的视频数据的实时传输。实践表明,该程序解决了图像采集和图像实时处理之间的关系,简化了应用程序对驱动的调用。……
  • 所需E币: 3
    时间: 2019-12-25 03:34
    大小: 333.12KB
    上传者: 238112554_qq
    本文档深入浅出的介绍了MFC的使用基础,循序渐进,是非常好的参考书!……