类的基本思想是数据抽象和封装.
数据抽象是一种依赖于接口和实现的分离的编程技术.
封装实现了类的接口和实现的分离.
7.1定义抽象数据类型
定义一个抽象数据类型使用关键字struct或是clas(差别仅在于默认访问权限修饰符).
//这是一个简单的类定义
class Student{ //类名叫Student
string name; //string类型的成员变量name
void sayHello(){ //返回值为vod的成员函数sayHello
std::cout<<"hello"<<std::endl;
}
}
定义在类内部的函数是隐式的inline函数(内联函数,前一章有简单介绍).所有成员都必须在类的内部声明,不过成员函数的函数体可以在类外定义,但是要表明所属的类,因为类本身就是一个作用域.并且在类外定义成员函数必须与类内的声明一致(返回类型,参数列表,函数名)
如上例子就是将成员函数sayHello声明在了类内,但是函数体定义在了类外.请暂时忽略掉类中的public关键字.先注意类外怎么定义函数体,首先是函数的返回值,然后是类名加上两个冒号::,然后是函数名和参数列表.
类中的成员函数可以使用名为this的额外的隐式参数来访问调用它的那个对象.
如上例子,在成员函数sayHello中使用了this->name访问到了成员变量name,main函数中s调用成员函数sayHello,函数体内的this就是指向s的指针.
若是在成员函数的声明后加上const关键字,那么该成员函数被称为常量成员函数.作用是修改隐式this指针的类型,默认情况下this的类型是指向类类型非常量版本的常量指针(有点绕),加上关键字const后的成员函数内的this指针是一个指向常量的指针,简单来说在常量成员函数中不能修改任何成员变量.如下例子,加上const修饰成员函数后就不能再修改成员变量了.
类通过一个或几个特殊成员函数来控制其对象的初始化过程,这些函数被称为构造函数,无论何时只要类的对象被创建,就会执行构造函数.
构造函数可以重载.
构造函数没有返回值.
构造函数可以在类外定义.
如果没有定义任何一个构造函数,那么编译器会隐式地定义一个默认构造函数,默认构造函数无需任何实参,该默认构造函数按照以下规则进行初始化:
1,如果存在类内初始值,那么会用类内初始值来初始化成员
2,否则按照类型的默认值来初始化.
如果我们定义了一个有参数的构造函数,但是又想要默认构造函数(如果有定义构造函数,那么编译器不生成默认构造函数),可以在参数列表后加上=default,例如:
#include <iostream>
using namespace std;
class Student {
string name="张三";
int age = 0;
public:
Student(string name) { //构造函数重载
this->name = name;
}
Student(int age) { //构造函数重载
this->age = age;
}
Student(string n, int a) :name(n), age(a) { //构造函数重载
cout << "hello world" << endl;
}
Student() = default; //使用默认构造函数
};
我们可以从上面的例子看到第三个构造函数和其他的构造函数不太一样,参数列表后面跟着的是构造函数初始值列表,可以直接给成员变量初始化赋值.
7.2访问控制与封装
C++中通过访问说明符加强类的封装性.(参考上面的代码)
public说明符后的成员可以在整个程序类访问.
private说明符后的成员仅能被类内的成员函数访问.
class和struct定义的抽象数据类型唯一的区别在于默认访问权限不一样.class默认是private,而struct默认是public.
类可以允许其他类或者函数访问它的非公有成员(private),只需要将其变成类的友元,只需增加以friend关键字开始的函数声明语句(不能代替函数的声明).友元声明只能写在类内最好是集中写在类的开头或结尾.
#include <iostream>
using namespace std;
class Student {
friend void sayHello(Student s); //意为函数sayHello可以访问本类的私有成员(private)
private:
string name="张三";
int age = 0;
public:
Student(string n, int a) :name(n), age(a) {
cout << "hello world" << endl;
}
};
7.3类的其他特性
成员函数也可以重载,只要满足条件就可以(函数同名,参数列表不一样)
之前说过const修饰的成员函数(常量成员函数)不能修改成员变量,但是如果在定义成员变量的时候在开头加上mutable关键字就可以对其进行修改.mutable修饰的变量称为可变数据成员.
即是两个类的成员列表完全一直,但它们也是不同的类型.
如果指定一个类为友元类,那么那个友元类内的所有成员函数都可以访问私有成员,如果要指定某个类的成员函数为友元函数,那么需要指出该成员函数为哪个类的成员函数.
声明友元函数时,若函数为重载函数,那么尽管函数名一样,但仍需要一个个单独声明友元.
7.4类的作用域
一个类就是一个作用域.
编译器处理完类中的全部声明后才会处理成员函数的定义,所以类外定义的成员函数,需要在声明之后.
一般来说,不建议使用成员的名字作为某个成员函数的参数名.
7.5构造函数再探
如果成员const,引用,或者属于某种未提供默认构造函数的类类型,那么我们必须通过构造函数初始值列表为这些成员提供初值.
最好让构造函数初始值的顺序与成员声明的顺序保持一致.尽量避免使用某些成员初始化其他成员.
C++11新标准使得可以定义委托构造函数,委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程.
7.6类的静态成员
在成员的声明前加上关键字static则将其变为静态成员,所有类的对象共享一个静态成员.
当我们指向类外部的静态成员时,必须指明成员所属的类名.static关键字只出现在类内部的声明语句中.
通常情况下,类的静态成员不应该在类的内部初始化.