C++Primer学习笔记第七章类

定义抽象数据类型

  • 类背后的基本思想数据抽象(data abstraction)和封装(encapsulation)。
  • 数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程技术。

类成员 (Member)

  • 必须在类的内部声明,不能在其他地方增加成员。
  • 成员可以是数据,函数,类型别名。

类的成员函数

  • 成员函数的声明必须在类的内部。
  • 成员函数的定义既可以在类的内部也可以在外部。
  • 使用点运算符 . 调用成员函数。
  • 必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
  • ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
  • 默认实参: Sales_item(const std::string &book): isbn(book), units_sold(0), revenue(0.0) { }
  • *this
    • 每个成员函数都有一个额外的,隐含的形参this
    • this总是指向当前对象,因此this是一个常量指针。
    • 形参表后面的const,改变了隐含的this形参的类型,如 bool same_isbn(const Sales_item &rhs) const,这种函数称为“常量成员函数”(this指向的当前对象是常量)。
    • return *this;可以让成员函数连续调用。
    • 普通的非const成员函数:this是指向类类型的const指针(可以改变this所指向的值,不能改变this保存的地址)。
    • const成员函数:this是指向const类类型的const指针(既不能改变this所指向的值,也不能改变this保存的地址)。

非成员函数

  • 和类相关的非成员函数,定义和声明都应该在类的外部。

类的构造函数

  • 类通过一个或者几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数
  • 构造函数是特殊的成员函数。
  • 构造函数放在类的public部分。
  • 与类同名的成员函数。
  • Sales_item(): units_sold(0), revenue(0.0) { }
  • =default要求编译器合成默认的构造函数。(C++11)
  • 初始化列表:冒号和花括号之间的代码: Sales_item(): units_sold(0), revenue(0.0) { }

访问控制与封装

  • 访问说明符(access specifiers):
    • public:定义在 public后面的成员在整个程序内可以被访问; public成员定义类的接口。
    • private:定义在 private后面的成员可以被类的成员函数访问,但不能被使用该类的代码访问; private隐藏了类的实现细节。
  • 使用 class或者 struct:都可以被用于定义一个类。唯一的却别在于访问权限。
    • 使用 class:在第一个访问说明符之前的成员是 priavte的。
    • 使用 struct:在第一个访问说明符之前的成员是 public的。

友元

  • 允许特定的非成员函数访问一个类的私有成员.
  • 友元的声明以关键字 friend开始。 friend Sales_data add(const Sales_data&, const Sales_data&);表示非成员函数add可以访问类的非公有成员。
  • 通常将友元声明只指定访问的权限,函数声明应该与类本身放在同一个头文件内(类外)
  • 类之间的友元:
    • 如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。

封装的益处

  • 确保用户的代码不会无意间破坏封装对象的状态。
  • 被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。

类的其他特性

定义类型的成员先出现;

  • 成员函数作为内联函数 inline
    • 在类的内部,常有一些规模较小的函数适合于被声明成内联函数。
    • 定义在类内部的函数是自动内联的。
    • 在类外部定义的成员函数,也可以在声明时显式地加上 inline
  • 可变数据成员 (mutable data member):
    • mutable size_t access_ctr;
    • 永远不会是const,即使它是const对象的成员。
  • 类类型
    • 每个类定义了唯一的类型。
    • 类的前项声明:不完全类型
      class Y;
      
      class X{
          Y* y = nullptr;	
      };
      
      class Y{
          X x;
      };
#include <iostream>
class Screen {
public:
	typedef std::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[cursor];
	}
	inline char get(pos ht, pos wd) const;
	Screen &move(pos r, pos c);
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	std::string contents;
};

1.在之后提供了构造函数,单对contents未初始化,=default要求编译器合成默认的构造函数;

2.类内隐式内联函数,可以显示表示出来;

3.常量成员函数,在参数列表后明确const属性;

inline	Screen &Screen::move(pos r, pos c) {
	pos row = r*width;
	cursor = row + c;
	return *this; //左值返回对象
}

在类外说明inline;

友元

1.类之间的友元:友元类的成员函数可以访问类所有成员;不具有传递性;

2.其他类的成员函数为友元::先定义其他类,声明成员函数;再声明类和友元声明;定义其他类成员函数;

3.重载函数需要分别声明友元

类的作用域

  • 每个类都会定义它自己的作用域。在类的作用域之外,普通的数据和函数成员只能由引用、对象、指针使用成员访问运算符来访问。
  • 函数的返回类型通常在函数名前面,因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外。
  • 如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。
  • 类中的类型名定义都要放在一开始。

构造函数再探

1.构造函数初始值列表:使用赋值的方式有时候不行,比如const或者引用类型的数据,只能初始化,不能赋值。(注意初始化和赋值的区别)养成使用构造函数初始值的习惯

2.初始化的顺序与类定义一致;初始化和成员声明的顺序保持一致

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

委托构造函数 (delegating constructor, C++11

  • 委托构造函数将自己的职责委托给了其他构造函数;可以继续传递
  • 加入函数体包含代码,先执行,然后控制权交还给委托者函数体;
  • Sale_data(): Sale_data("", 0, 0) {}
class Book 
{
public:
    Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate)
        :isbn_(isbn), name_(name), author_(author), pubdate_(pubdate)
    { }

    Book(unsigned isbn) : Book(isbn, "", "", "") {}

    explicit Book(std::istream &in) 
    { 
        in >> isbn_ >> name_ >> author_ >> pubdate_;
    }

private:
    unsigned isbn_;
    std::string name_;
    std::string author_;
    std::string pubdate_;
};

explicit关键字

只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).

优点:可以抑制构造函数定义的隐式转换

缺点:为了转换要显式地使用构造函数

vector <NoDefault> vec(10);//不合法,未对NoDefault没有默认构造函数;

隐式的类型转换

  • 如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。这种构造函数又叫转换构造函数(converting constructor)。
  • 编译器只会自动地执行仅一步类型转换。
  • 抑制构造函数定义的隐式转换:
    • 将构造函数声明为explicit加以阻止。
    • explicit构造函数只能用于直接初始化,不能用于拷贝形式的初始化。

可以使用explicit的构造函数 显式强制转换:

item.combine(Sale_date(null_book));实参是一个显示构造的Sale_date对象;

聚合类 (aggregate class)

  • 满足以下所有条件:
    • 所有成员都是public的。
    • 没有定义任何构造函数。
    • 没有类内初始值。
    • 没有基类,也没有virtual函数。
  • 可以使用一个花括号括起来的成员初始值列表,初始值的顺序必须和声明的顺序一致。

字面值常量类

  • constexpr函数的参数和返回值必须是字面值。
  • 字面值类型:除了算术类型、引用和指针外,某些类也是字面值类型。
  • 数据成员都是字面值类型的聚合类是字面值常量类。
  • 如果不是聚合类,则必须满足下面所有条件:
    • 数据成员都必须是字面值类型。
    • 类必须至少含有一个constexpr构造函数。
    • 如果一个数据成员含有类内部初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。
    • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

constexpr构造函数:

字面值类型的常量构造函数可以,既符合构造函数要求,又符合constexpr函数的要求;

必须初始化所有数据成员,用constexpr或常量表达式

7.6类的静态成员

成员与类本身相关  ——声明:static 

1.独立于该类类型的对象中,被所有对象共享  2.不包含,不能使用this指针 3.不能声明成const

使用:类的作用域运算符;通过对象来访问;

定义:
1.静态成员函数的定义在类内部或者外部,在外部不需要重复static关键词
2.静态成员在类的外部定义和初始化(最好和其他静态数据成员和非内敛函数放在同一文件):
double Account::interestRate = initRate();
3.如果一定要在类内部定义,则要求必须是字面值常量类型的constexpr,初始值是常量表达式
static constexpr int period=30;//

注意:1.静态数据成员可以是不完全类型的,可以是他所属的类类型;非静态数据成员只能声明所属类的指针或引用;
2.可以作为默认实参

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值