class
类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。封装实现了类的接口和实现的分离。
成员函数
成员函数必须在类的内部声明,它的定义则既可以在类的内部也可以在类的外部。定义在类内部的函数是隐式的inline函数。非成员接口函数的声明与定义都在类的外部。
this
this总是指向“这个”对象,所以this是一个常量指针。不可以改变this中保存的地址。
成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。当调用total.isbn()时,编译器负责把total的地址传递给isbn的隐式形参this,可以等价地认为编译器将该调用重写成了如下形式:
// 伪代码,说明调用成员函数的实际执行过程
Sales_data::isbn(&total)
当需要返回this对象时,可直接使用
return *this // 返回调用该函数的对象
return 语句解引用this指针以获得执行该函数的对象,也可以说是返回total的引用。
引入const成员函数
常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
类相关的非成员函数
类的一些辅助函数,概念上属于类的接口的组成部分,但实际上并不属于类本身。一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件内。
构造函数
构造函数用于初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。构造函数与类名相同,无返回类型,可以包含多个构造函数,不能被声明成const。
就算是创建const对象,也要在构造函数完成初始化后才能取得其“常量”属性,因此构造函数在const对象构造过程中可以向其写值。
合成的默认构造函数
编译器创建的构造函数又称为合成的默认构造函数。大多情况下其会按照如下规则初始化类的数成员:1、如果存在类内的初始值,用它来初始化成员、2、默认初始化该成员。
某些类不能依赖于合成的默认构造函数
1、只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数
2、如果类包含有内置类型或者复合类型地成员,则只有当这些成员全部被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数。(此种情况下内置类型或复合类型的值是未定义的)
3、有些时候编译器不能为某些类合成默认的构造函数。例如,类中包含其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。
==default
Sales_data() = default;
当既需要其他形式的构造函数,也需要默认的构造函数时,可这样定义。= default要求编译器生成构造函数。当=default在类的内部时,默认构造函数是内联的;如果在外部,则该成员默认情况下不是内联的。
构造函数初始化列表
Sales_data(const std::string &s):bookNo(s){}
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n){}
构造函数初始化列表,负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(包括花括号)成员初始值。不同成员的初始化通过逗号分隔开来。
Sales_data(const std::string &s):bookNo(s){}
// 等价于
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(0), revenue(0){}
构造函数只要执行了,就算构造函数初始化列表是空的,对象的成员也会被初始化。
拷贝、赋值和析构
除了定义类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。编译器会提供默认的实现,一般会对对象的所有成员执行拷贝、赋值和销毁操作。
total = trans; // 处理下一本书
// 等价于
total.bookNo = trans.bookNo;
total.revenue = trans.revenus;
对于某些类来说,编译器合成的版本无法正常工作。特别是,当类需要分配类对象之外的资源时,合成的版本常常会失效。管理动态内存的类通常不能依赖于上述操作的合成版本。
**管理动态内存的类能(而且应该)使用vector对象或者string对象管理必要的存储空间。**若类中包含vector或string成员,则其拷贝、赋值和销毁的合成版本能够正常工作。
访问控制与封装
public 、private
一个类中可以包含0个或多个访问说明符,而且对于某个访问说明符能出现多少次也没严格限定。每个访问说明符的有效范围直到出现下一个访问说明符或到达类的结尾处为止。
class 与 struct
class与struct都可以用于定义类,区别在于二者的默认访问权限不太一样(唯一区别)。类可以在它的第一个访问说明符之前定义成员,对这种成员的访问权限依赖于类定义的方式。
struct时第一个访问说明符之前的成员是public。class时这些成员时private的。当希望类所有成员是public时,使用struct;反之,如果希望成员是private的,使用class。
友元
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。弊端是:牺牲了封装性与可维护性。
当类的数据成员是private时,类的一些辅助函数,由于不是类的成员导致无法使用数据。
class Hello
{
public:
// 友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。
friend void print(const Hello&);
Hello();
~Hello();
void add();
private:
int a = 2;
};
// 函数声明
void print(const Hello&);
#include "stdafx.h"
#include "Hello.h"
#include <iostream>
Hello::Hello()
{
std::cout << "hello zhu" << std::endl;
}
Hello::~Hello()
{
}
void Hello::add()
{
}
void print(const Hello& a)
{
std::cout << " aaa " << a.a << std::endl;
}
友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。友元不受它所在区域访问控制级别的约束。一般来说,最好在类定义开始或结束前的位置集中声明友元。
友元函数的声友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。但是很多编译器并未强制限定友元函数必须在使用之前在类的外部声明。但最好提供一个独立的函数声明。
类的其他特征
类中使用类型别名
typedef std::string::size_type pos; //存在访问限制,可以是public 或 private的一种。
// using pos = std::string::size_type; // 等价上面
// 在类外可通过命名空间访问,Screen::pos
用来定义类型的成员必须先定义后使用,不同于普通成员。因此类型成员通常出现在类开始的地方。
#include <string>
class Screen
{
public:
typedef std::string::size_type pos;
public:
Screen() = default; // 已有构造函数,需显式声明才会使编译器合成默认构造函数
Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){}
char get() const { return contents[cursor]; } //读取光标处字符,隐式内联
inline char get(pos ht, pos wd) const; //显式内联,在之后实现
Screen &move(pos r, pos c); // 能在之后设为内联
private: //三个string::size_type类型的成员
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
char Screen::get(pos r, pos c) const
{
pos row = r * width; // 计算行的位置
return contents[row + c]; // 返回给定列的字符
}
// 在函数定义处指定inline
inline Screen & Screen::move(pos r, pos c)
{
pos row = r * width; // 计算行的位置
cursor = row + c; // 在行内将光标移动到指定的列
return *this; // 以左值的形式返回对象
}
定义在类内部的成员函数是自动inline的,故get函数默认是inline函数。同时可以在类的内部把inline作为声明的一部分显式地声明成员函数,也能在类的外部用inline关键字修饰函数的定义。可以在声明和定义的地方同时说明inline,但最好只在类外部定义的地方说明inline。
内联函数内联函数必须写在头文件。
可变数据成员
一个可变数据成员永远不会是const,即使它是const对象的成员。任何成员函数,包括const 函数在内都能改变它的值。
public:
void some_member() const;
private:
mutable size_t access_ctr;
// ----- cpp ------
void Screen::some_member() const
{
++access_ctr;
}
类内初始值
std::vector<Screen> screens{ Screen(24, 80, ' ') };
//std::vector<Screen> screens = { Screen(24, 80, ' ') };
提供类内初始值时,必须以=或花括号表示。
返回*this的成员函数
public:
Screen &set(char);
Screen &set(pos, pos, char);
使用:
int main()
{
Screen myScreen = Screen(5, 5, 'X');
Screen::pos a2 = 4; //导入命名空间的成员
myScreen.move(4, 0).set('%');
myScreen.move(4, 0);
myScreen.set('^');
myScreen.set(4, 0, '&');
return 0;
}
成员函数的返回类型为引用,所以可以修改myScreen的值。
Screen temp = myScreen.move(4, 0); // 对返回值进行拷贝
temp.set('*');
返回副本不会修改myScreen的值。
基于const的重载
public:
const Screen& display(std::ostream& os) const { do_display(os); return *this; }
Screen& display(std::ostream& os) { do_display(os); return *this; }
private:
void do_display(std::ostream &os) const { os << contents; }
const 和 非 const 成员函数被视为不同的函数签名,即使它们的参数列表完全相同。 const关键字位于函数声明的最后,表示这是一个常量成员函数。这种函数只能被常量对象调用,常量对象不能修改其成员变量,所以这个函数保证了不会修改任何成员变量。返回值是 const Screen&,表示返回的是一个常量引用,意味着调用者不能通过这个返回值修改 Screen`对象。
const Screen screen;
screen.display(os); // 调用的是 const 版本
Screen screen;
screen.display(os); // 调用的是非 const 版本
文章评论