类的基本思想是数据抽象和封装
数据抽象是一种依赖于接口和实现分离的编程技术
类的接口包括用户所能执行的操作 类的实现包括类的数据成员、实现接口的函数体以及定义类所需的各类私有函数
封装实现了类的接口和实现的分离。封装后的类隐藏了实现的细节,用户只能使用接口而无法访问实现部分
类的成员
定义在类内部的函数是隐式的内联函数
成员函数通过一个名为this
的隐式参数(类型为常量指针)来访问调用它的对象
当调用一个成员函数时 用请求该函数的对象地址初始化this
eg. total.get()
等价于sales_data:: get(&total)
因此 我们可以直接在成员函数内部调用this
并且也不能显示声明一个名为this
的形参
int get() {
retrun this->num;
}
如果要定义一个常量对象使用的函数 则应该为
int get() const{
return this->num;
}
通过后置的const
修改隐式参数this
的类型
编译器处理类时 先编译类成员声明 再编译类成员函数体(如果有的话)
因此 成员体函数可以随意使用类中的其他成员而无须在意出现次序
构造函数
类的构造函数没有返回类型 也不能被声明成const
只有当没有显式地定义构造函数的时候 编译器才会隐式地定义一个合成的默认构造函数:
这个函数对有初始值的成员使用初始值初始化,对未定义初始值的采用默认初始化
如果我们定义了其他构造函数 那么也必须显式定义一个默认构造函数 在C++中可以用 =default 来执行默认操作
eg. Sales_data() = default
构造函数可以通过初始值列表为成员初始化
其初始化顺序与这些成员在类中定义的顺序一致 与在初始化列表中的顺序无关
这与通过构造函数赋值的区别在于 部分类型不能赋值 必须初始化 如引用 或者常量类型变量
Class Sales_data{
public:
Sales_data(int a , double p) ;
private:
const int bookno; double &revenue;
};
Sales_data(int a , double p): bookno(a), revenue(p) { }
Sales_data(int a , double p){
bookno = a;
revenue = p
} //错误 引用和常量都必须在初始化时赋值
C++还允许委托构造函数 将初始值列表输入给另一个已定义的构造函数 先执行被委托的构造函数代码 再执行委托函数体内的代码
Sales_data(int a , double p) ;
Sales_data(): Sales_data(0, 0) { }
Sales_data(int a): Sales_data(a, 0) { }
当构造函数只接受一个实参时 这个构造函数事实上定义了从该实参类型转换为此类类型的隐式转换机制 也叫做转换构造函数
注意 编译器只会自动执行一步类型转换
Sales_data(string &s): bookno(s){}
Sales_data:: combine(Sales_data &a, Sales_data& b){}
String str = "123";
Sales_data a;
a.combine(str); //成立 string对象会通过构造函数隐式转换成Sales_data类型
a.combine("123"); //错误 字符串字面值首先需隐式转换为string对象 再转换为Sales_data类型 不止一步
但是 可以通过explicit
关键字抑制这种隐式转换 在开头添加了这个关键字的构造函数不会被用于隐式转换
但同时也不能进行拷贝形式的初始化了 只能直接初始化
不过我们仍然可以通过显示调用来强制类型转换
如果没有定义类的拷贝 赋值和析构函数的话 编译器会默认合成
编译器生成的函数将对类对象的每个成员都执行拷贝 赋值和销毁操作
访问权限控制
通过访问说明符 public
和 private
可以加强封装性 访问说明符的有效范围直到出现下一个访问
说明符或者类结束
class
和struct
关键字定义的唯一区别就是默认访问权限不同
若在未定义访问说明符之前定义成员 struct
默认是public
class
默认是private
类可以通过在类内将其他类或函数使用friend
关键字声明为友元来允许其他类或者函数访问它的非公有成员
友元的声明仅仅指定了访问的权限而非真正的函数声明 如果希望调用该函数 还需在友元声明之外在类外单独进行一次声明
友元函数的定义可以在类内(属于隐式内联函数) 也可以在类外
但是即使是在类内定义了友元函数 仍然需要在类外声明后才能使用
如果一个类指定了友元类 则有友元类的成员可以访问此类包括非公有成员在内的所有成员
也可以单独指定某个类的成员为友元 如 friend void windows_mgr:: clear(screen index);
重载函数被声明成友元必须每个单独声明 否则只有被声明的那个版本为友元
友元不具备传递性
封装的优点:
- 确保用户代码不会无意间破坏封装对象的状态
- 被封装的类的具体实现细节可以随时改变而无需调整用户级别的代码
常量成员与静态成员
可以通过mutable
关键字将一个成员声明为可变数据成员
一个可变数据成员永远不会是const
即使它是一个const
对象的成员
并且即便是const
成员函数也可以改变一个可变成员的值
一个const
成员函数如果以引用的方式返回*this
那么返回类型是常量引用
由于非常量类型的成员函数对于常量对象是不可用的 所以我们需要对成员函数声明const
重载函数来使得可适用
当我们提供一个类内初始值时 必须以符号=
或者花括号{ }
表示
类的非静态成员类型不能是类本身 因为只有类被定义以后才能知道所需的空间
但可以是指向类的指针或者引用
不过静态成员类型可以是类本身
如果使用类中定义的别名作为成员函数的返回类型 在类外定义时必须使用类作用域说明 因为返回类型定义在函数之前
但是函数中使用类中定义时 无需作用域符说明 因为函数已经被声明在作用域中了
windows_mgr:: screenindex windows_mgr::addscreeen( ){
screenindex a= 1;
}
int windows_mgr:: addscreeen( ){
screenindex a= 1;
}
如果在类中定义了与全局作用域同名的变量 那么在类的成员函数中将把外部全局作用域的变量隐藏
聚合类
聚合类定义:
- 所有成员都是public
- 没有任何构造函数
- 没有类内初始值
- 没有基类或virtual函数
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法
类的静态成员不与任何对象绑定 也不包含this
指针
类的静态成员可以通过类作用域直接访问 也可以通过某个类的对象访问
应该在类的外部定义和初始化每个静态成员 而不能在构造函数内初始化 除非是作为常量静态
成员 在声明时直接用常量表达式初始化
但即使一个常量静态成员在内部被初始化了 通常情况下也应该在类的外部定义一下该成员
在类的外部定义静态成员时 不能重复static关键字 该关键字只能出现在类内的声明语句中
静态成员可以是本身所属类的类型 也可以作为默认实参 而非静态两者都不行
前向声明: 对尚未定义的名字的声明, 通常用于表示位于类定义之前的类声明
不完全类型: 已经声明但尚未定义的类型 不完全类型不能用于声明变量或者类的非静态成员
但可以用于定义指针和引用