【C++Primer】第7章:类

简介: 【C++Primer】第7章:类

第7章 类


7.1 定义抽象类数据类型


成员函数的声明必须在类的内部,它的定义则既可以在类的内部也可以在类的外部


string isbn() const {return bookNo;}
string isbn() const {return this->bookNo;}


this的目的总是指向“这个”对象,所以this是一个常量指针


const的作用是修改隐式this指针的类型


默认情况下,this的类型是指向类类型非常量版本的常量指针;例如Sales_data *const


const使其成为指向常量的常量指针;const Sales_data *const


类似于:


std::string Sales_data::isbn(const Sales_data *const this){
    return this->isbn;
}


成员函数体可以随意使用类中的其他成员而无需在意这些成员出现的次序。


定义read和print函数


read函数从给定流中将数据读到给定的对象里,print函数则负责将给定对象的内容打印到给定流中。


IO类属于不能被拷贝的类型


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_slod << " "
       << item.revenue << " " << item.avg_price();
    return os;
}


构造函数


构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。


构造函数的名字与类名相同


构造函数没有返回值


构造函数不能被声明成const


构造函数在const对象的构造过程中可以向其写值


默认构造函数无需任何实参


编译器创建的构造函数又被称为合成的默认构造函数


  • 如果存在类内的初始值,用它来初始化成员


  • 否则,默认初始化该成员


只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数


如果类包含有内置类型或者复合类型的成员,则只有当这些成员全部被赋予了类内的初始值时,这个类才适合使用合成默认构造函数


构造函数也可以重载


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 &);


构造函数不应该轻易覆盖掉类内初始值,除非新赋的值与原值不同


在类外部定义构造函数


Sales_data::Sales_data(istream &is){
    read(is, *this)  //从is中读取一条交易信息然后存入this对象中
}


使用this来把对象当成一个整体访问


7.2 访问控制与封装


  • 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口


  • 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。private部分封装了类的实现细节


使用class和struct定义类唯一的区别就是默认的访问权限不同。struct默认为public,class默认为private


友元


类可以允许其他类或者函数访问它的非公有成员,用友元friend


友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。


友元不是类的成员也不受它所在区域访问控制级别的约束


一般来说,最好在类定义开始或结束前的位置集中声明友元


封装的两个优点:


  • 确保用户代码不会无意间破坏封装对象的状态


  • 被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码


友元的声明仅仅指定了访问的权限,而非一个个通常意义上的函数声明


7.3 类的其他特性


可变数据成员


mutable关键字


class Screen{
public:
  void some_member() const;
private:
    mutable size_t access_ctr;      //mutable关键字
};
void Screen::some_member() const{   //就算是const也能修改
    ++access_ctr;
}


设置为内联函数:在类内声明或者类外定义加上一个inline就行


返回*this的成员函数


class Screen{
public:
    Screen &set(char);
    Screen &set(pos, pos, char);
};
inline Screen &Screen::set(char c){
    contents[cursor] = c;
    return *this;
}
inline Screen &Screen::set(pos r, pos col, char ch){
    contents[r*width + col] ch;
    return *this;
}


myScreen.move(4,0).set('#');


类类型


每个类定义了唯一的类型。即使两个类的成员列表完全一致,它们也是不同的类型


我们可以把类名作为类型的名字使用,从而直接指向类类型


Sales_data item1;
class Sales_data item1; //等价


类的声明


class Screen;   //Screen类的声明


此时我们已知Screen是一个类类型,但是不清楚它到底包含哪些成员


对于一个类来说,在我们创建它的对象之前该类必须被定义过,而不能仅仅被声明


一个类的成员类型不能是该类自己


然而一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),

因此类允许包含指向自身类型的引用或指针


class Link_screen{
  Screen window;
    Link_screen *next;
    Link_screen *prev;
};


友元再探


函数可以定义为友元,类也可以,类的成员函数也可以


class Screen{
    friend class Window_mgr;                      //类做友元
    friend void Window_mgr::clear(ScreenIndex);   //成员函数做友元
};


友元关系不存在传递性


如果一个类想把一组重载函数声明成它的友元,它需要对这组函数中的每一个分别声明。


类和非成员函数的声明不是必须在它们的友元声明之前。当一个名字第一次出现在一个友元声明中时,我们隐式地假定该名字在当前作用域中时可见的。


7.4 类的作用域


类外定义时:返回类型也需指明作用域


class Window_mgr{
public:
  ScreenIndex addScreen(const Screen&);  
};
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen&) {
    /***/
}


名字查找与类的作用域


typedef string Type;
Type initVal();
class Exercise {
public:
  typedef double Type;
  Type setVal(Type);   //double
  Type initVal();      //double
private:
  int val;
};
Exercise::Type Exercise::setVal(Type parm) {  //double
  val = parm + initVal();  //double-->int
  return val;              //int--->double
}


7.5 构造函数在探


构造函数初始值列表


class ConstRef {
public:
  ConstRef(int ii);
private:
  int i;
  const int ci;
  int& ri;
};


错误类外构造函数初始化如下:


ConstRef::ConstRef(int ii){  //赋值初始化
    i = ii;
    ci = ii;  //错误:不能被ci赋值
    ri = ii;  //错误
}


初始值列表初始化:


ConstRef::ConstRef(int ii) : i(ii), ci(ii), ri(ii) {}  //正确


养成使用构造函数初始值的习惯


构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序


class X{
  int i;
    int j;
public:
    //未定义的i在j之前被初始化
    X(int val) : j(val), i(j) {}
};


最好令构造函数初始值的顺序与成员声明的顺序保持一致;尽量避免使用成员函数初始化其他成员函数


如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。


委托构造函数


C++11新标准


委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些职责委托给了其他构造函数


class Sales_data{
public:
  Sales_data(string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt*price) {}
    //其他构造函数委托上一个构造函数
    Sales_data() : Sales_data("", 0, 0) {}
    Sales_data(string s) : Sales_data(s, 0, 0) {}
};


聚合类


聚合类使得用于可以直接访问其成员,并且具有特殊的初始化语法形式


聚合类条件:


  • 所有成员都是public的


  • 没有定义任何构造函数


  • 没有类内初始值


  • 没有基类,也没有virtual函数


初始化聚合类:


struct Data{
  int ival;
  string s;
};
Data vall = {0, "anna"};  //参数要一一对应
Data vall2 = {"anna", 0};  //错误


字面值常量类


数据成员都是字面值类型的聚合类;或者满足一下要求:


  • 数据成员都必须是字面值类型


  • 类必须至少含有一个constexpr构造函数


  • 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数


  • 类必须使用析构函数的默认定义,该成员负责销毁类的对象


尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。


正常构造函数前面加constexpr变成constexpr构造函数。


constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式


7.6 类的静态成员


有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。


静态数据成员的类型可以是常量、引用、指针、类类型等。


类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。


静态成员函数不与任何对象绑定在一起,它们不包含this指针


与其他成员函数一样,既可以在类内部也可以在类外部定义静态成员函数


当在类外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句


静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。


意味着它们不是由类的构造函数初始化的。


一般来说不能在类内部初始化静态成员;必须在类的外部定义和初始化每个静态成员


可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr


class A{
    static constexpr int period = 30;  
};


即使一个常量静态成员在类内被初始化了,通常情况下也应该在类外定义以下该成员

静态成员独立于任何对象


静态数据成员可以是不完全类型


class Bar{
public:
  //...
private:
    static Bar mem1;  //正确,静态成员可以是不完全类型
    Bar *mem2;        //正确,指针也行
    Bar mem3;         //错误,数据成员必须是完全类型
}


与普通成员不同,静态成员可以作为默认实参


class Screen{
public:
  Screen& clear(char = bkground);
private:
    static const char bkground;   //去掉static就报错
};


非静态成员不能作为默认实参,因为它的值属于对象的一部分

目录
相关文章
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
106 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
97 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
114 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
32 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
2月前
|
存储 编译器 C语言
【C++类和对象(上)】—— 我与C++的不解之缘(三)
【C++类和对象(上)】—— 我与C++的不解之缘(三)