10.2.2 C++中的类
类是一种将抽象转换未用户定义类型的C++工具,它将数据表示和操作数据的方法合成一个整洁的包。
接口:一个共享框架,供两个系统交互时使用。
1.访问控制
使用类对象的程序可以直接访问类的公有部分,但只能通过公有成员函数访问对象的私有成员
防止程序直接访问数据被称为数据隐藏。
数据隐藏不仅可以防止直接访问数据,而且还让开发者无需了解数据是如何被表示的。
将实现细节放在一起并将它们与抽象分开称为封装
2.private和public
在类中使用private来强调成员是私有的,public强调成员是公有的。
在class声明的类中,成员默认是私有的,而在struct中成员默认是公有的。
10.2.3实现类的成员函数
定义成员函数时,使用作用域解析运算符::来来标识函数所属的类
类方法可以访问类的private组件
如stock是一个类名,它有一个叫fun的成员函数
void stock::fun()
{
。。。。
}
1.内联函数
定义在类声明中的函数都将自动成为内联函数,类声明常将短小的成员函数作为内联函数,如果想在类外声明内联函数,可以使用inline关键字。
内联函数要求每个使用它们的文件中都对其进行定义,为了方便使用内联函数,我们将内联函数放在类的头文件中。
2.方法使用哪个对象
创建对象: 类名 对象名(类名相当于一种数据类型 对象名是变量)
使用成员函数方法 对象名.函数名(参数)
10.3类的构造函数和析构函数
对象的变量不能像初始化int变量 那样来进行初始化,因为对象的存在私有成员,在类外无法进行访问。而类的成员函数可以访问这些成员变量,因此我们可以使用类的成员函数对对象进行初始化,而类的构造函数就是C++用来初始化对象的一个函数。
构造函数在创建对象的时候会被自动调用。
构造函数:
名字必须与类名一致,且没有返回类型,可以有参数,可以重载(也就是可以有多个构造函数)。如fun类的构造函数为 fun();
若不定义构造函数,程序将提供一个默认的隐藏的构造函数,而这个构造函数并不进行任何操作。若手动定义了多个构造函数,
程序则会根据定义对象时候括号里的参数,来确定使用哪一个构造函数。
如果提供了非默认的构造函数,下列语句会出错
fun A;定义一个fun类的对象A
有两种方法可以解决这个问题,一是在类里加入一个默认的构造函数,二是给非默认的构造函数提供默认参数。
但是这两种方法不能同时用,如果同时用的话,fun A;可以使用任意一个构造函数,于是程序将不知道使用哪一个,会爆错。
列表初始化
听名字就是列一个表进行初始化
语法就是下面这种
-
#include <iostream>
-
using namespace std;
-
class A
-
{
-
int w;
-
public:
-
A(int j=0):w(j){}
-
};
-
int main()
-
{
-
A a;
-
return 0;
-
}
在函数名后加一个分号然后写出变量名 ,变量名后跟一个括号,意思是用括号里这个值来初始化这个变量
注意事项:
- 这种格式只能用于构造函数
- 必须用这种格式来初始化非静态const数据成员和引用数据成员
什么时候初始化列表是必用的呢?
1. 类成员为const类型
2. 类成员为引用类型
-
#include <iostream>
-
using namespace std;
-
class A
-
{
-
public:
-
A(int &v) : i(v), p(v), j(v) {}
-
void print_val() { cout << "hello:" << i << " " << j << endl;}
-
private:
-
const int i;
-
int p;
-
int &j;
-
};
-
int main()
-
{
-
int pp = 45;
-
A b(pp);
-
b.print_val();
-
}
构造函数 所说是起到初始化的作用,但是实际上是通过赋值的操作来进行初始化的,但是const变量 和引用无法通过赋值来进行初始化,所以要用初始化列表。
3. 类成员为没有默认构造函数的类类型
-
#include <iostream>
-
using namespace std;
-
class Base
-
{
-
public:
-
Base(int a) : val(a) {}
-
private:
-
int val;
-
};
-
class A
-
{
-
public:
-
A(int v) : p(v),b(v) {}
-
void print_val() { cout << "hello:" << p << endl;}
-
private:
-
int p;
-
Base b;
-
};
-
int main()
-
{
-
int pp = 45;
-
A b(pp);
-
b.print_val();
-
}
原因同样是创建对象时,要初始类成员的每一个成员
(如果没有在初始化列表里面,编译器会自动使用它的默认的构造函数进行初始化,但是它没有默认构造函数,所以会编译报错,所以没有默认构造函数的成员变量需要使用初始化列表进行初始化)
这里如果把初始化列表里的b删掉,就会爆错,同理类存在继承关系,派生类必须在初始化列表里初始化。
4. 如果类存在继承关系,派生类必须在其初始化列表中调用基类的构造函数
PS:若一个类中有其他类的对象,那么该如何初始化这个类呢?
只需在类的构造函数的初始化列表里加上其他类对象的名字及其参数列表即可。
派生类构造函数的执行顺序:
① 调用基类构造函数,初始化基类数据成员;
② 调用子对象构造函数,初始化子对象数据成员;
③ 执行派生类自身的构造函数,初始化自己新增数据成员。
析构函数
析构函数的特点:
- 一个类只有一个析构函数,不能重载
- 名字与类名相同,定义时候要加上~
- 没有参数
- 不能有返回值,没有返回值类型
对应于构造函数初始化对象,析构函数是用来释放对象的内存的,可以将delete等操作放在析构函数里,析构函数在对象离开其作用域的时候自动执行。
对象析构的顺序和创建的顺序相反,即先构造的后析构,后构造的先析构
但这并非绝对成立!!!!
在不同的作用域中的对象,或者具有不同存储类别的对象,调用构造函数和析构函数的顺序也会有所不同
- 一个程序中有多个文件。在多个文件中定义了全局对象,那么这些对象的执行顺序是不确定的;
- 在函数中定义局部自动对象,如果函数被多次调用;多次调用构造函数和析构函数;
- 如果函数中定义静态局部对象;那么函数调用结束时对象并不释放,只有main结束或调用exit时才调用析构函数。
什么时候调用析构函数?
- 如果在函数中定义了一个对象,当函数调用结束时,释放对象前自动执行析构函数
- static 局部对象在函数调用结束时,包含的对象不会被释放,只在main函数结束或调用exit函数时,才调用static局部对象的析构函数。
- 如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束,或exit语句),调用该全局对象的析构函数
- 如果用new运算符动态地建立了一个对象,当用delete 运算符释放对象时,先调用该全局对象的析构函数。
10.4this指针
众所周知,同一个类的多个对象公用一套方法,那么在调用同一套方法的时候,程序是如何知道要访问哪个对象里的成员的呢,答案是通过this指针,类的成员函数里有一个隐藏的指向对象的参数就是this指针。
具体看下面这个例子
-
#include <iostream>
-
using namespace std;
-
class Base
-
{
-
public:
-
Base(int a) : val(a) {}
-
void display();
-
private:
-
int val;
-
};
-
void Base::display()
-
{
-
cout<<val<<endl;//实际上就是cout<<this->val<<endl;
-
}
-
int main()
-
{
-
Base a(1),b(2);
-
a.display();//看上去没有传参数,实际上传了a的地址
-
}
10.5对象数组
对象数组跟结构体差不多,这里就讲讲一些不同点。
首先是初始化,由于对象有构造函数,所以我们在初始化对象数组的时候要用大括号括起来,将构造函数列出来,来进行初始化
例如
-
const int max=4;
-
fun a[max]={ fun(1),fun(2),fun(3),fun(5)}
-
如果类只有一个成员变量,则可以直接像数组那样赋值
-
如:fun b[max]={1,2,3,4};
10.6对象指针
什么是对象指针?
创建一个类的对象时,系统会为每一个对象分配一定的存储空间,以存放成员。对象空间的起始地址就是对象的指针。可以定义一个指针,用来存放对象的指针。
语法
-
time *pt;
-
time t;
-
pt=&t1; // 指针指向对象
对象指针的使用与普通指针使用差不多,具体可以自行百度指针的使用