该文仅作为个人学习总结,包含学习中的理解与感悟,望各位大佬不吝赐教。
类定义与类成员
定义一个类:
class Box
{
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
public:
double getArea();
};
定义的类有以下属性:类名、类成员(包括数据和函数)以及成员的访问属性。
一个类被定以后一般是无法直接使用的,需要将其实例化后才能使用这些被定义的类成员。
实例化一个类:
Box box_a;
对于初始化其中的类数据成员,目前有两种方式:
- 在类声明时将其初始化(C++11以后)
class Box
{
private:
double length{1}; // Length of a box
double breadth{2}; // Breadth of a box
double height{3}; // Height of a box
};
- 在构造函数中初始化(构造函数&析构函数)
访问属性:
类成员的访问属性通过类主体内部对各个区域标记 public、private、protected 来指定,这三个关键字被称作访问修饰符(access-specifier)。若不指定,则默认为私有成员。
-
公有成员(public):
类的外部可以访问,可以直接获取和设置公有变量的值。 -
私有成员(private):
只可通过类内部的成员访问,需要提供一些接口(类公有函数或友元函数)来获取和设置私有变量的值。 -
保护成员(protected):
类外部不可访问,但继承该类的派生类可以访问。
访问类成员:
使用 . 运算符访问类成员。
box.height = 1; //访问数据并赋值,若该成员为私有,编译器报错
box.getArea(); //访问类成员函数
构造函数&析构函数
构造函数与析构函数分别在创建和销毁类实例对象时被调用,定义这些函数以达到特定的效果。
1.构造函数
构造函数的函数名与类名相同,它不会返回任何类型,一般用于类对象的初始化。
class Box
{
private:
double length;
double breadth;
double height;
public:
Box(double a, double b, double c); //声明构造函数
};
//定义构造函数
Box::Box(double a=1.0, double b=2.0, double c=3.0)
{
length = a;
breadth = b;
height = c;
}
构造函数是否带参均可,对于带参数的构造函数,实例化对象时需要填入实参或使用默认参数。
Box box_a(1, 2, 3);
也可使用初始化列表初始化这些字段。
Box::Box(int a, int b, int c): length(a), breadth(b), height(c)
{
}
这种写法与上述定义功能相同。
Q:为何在类定义外部定义构造函数?
在类定义中定义的函数被认为是内联函数(inline),该类函数在被调用时,编译器会将其函数体副本直接复制并替换调用的语句。即以扩大存储空间为代价缩短运行时间。
对于一些简短的函数,可以提高执行效率;但对于构造函数和一些复杂的函数,提升的效率不大,且直接替换带来的风险较高,不推荐使用。
程序中定义一个内联函数,可以使用关键字inline。
inline int add(int a, int b)
{
return a + b;
}
- 析构函数
析构函数的函数名相对于类名前多了一个~,它不能带有任何参数,也不会返回任何类型。一般用于在跳出程序前释放资源。
class Box
{
private:
double length;
double breadth;
double height;
public:
~Box(); //声明析构函数
};
//定义构造函数
Box::Box()
{
cout << "everything is ready." << endl;
}
拷贝构造函数
拷贝构造函数的函数名与类名相同(即与构造函数的函数名相同),在以下情况被调用:
- 使用一个已经实例化的对象初始化另一个同类的对象。
Box box_a(1, 2, 3);
Box box_b = box_a; //此时并不是直接赋值,而会调用拷贝构造函数
- 把一个对象作为实参传入函数
- 从一个函数中返回一个对象
拷贝构造函数常见的写法如下:
class Box
{
private:
double length;
double breadth;
double height;
public:
Box(const Box &obj); //声明拷贝构造函数
};
Box::Box(const Box &obj)
{
length = obj.length;
breadth = obj.breadth;
height = obj.bread;
}
拷贝构造函数丰富了创建对象的方法,如果你没有定义类的拷贝构造函数,编译器也会帮你定义一个。
Q:为什么定义拷贝构造函数时,形参要使用const和引用(&)?
与变量的赋值不同,引用一个变量不会为其创建新的内存空间,相当于在原内存打上了一个新的标签,可以通过这个新标签来访问内存,引用被定义后无法更换指向的内存空间,但内存空间的内容可以改变。
在拷贝构造函数中,引用一个对象给新对象赋值,而不是为这个对象创建一个临时副本再赋值给新对象。因为创建副本时也会调用拷贝构造函数,这样就陷入了不断创建副本的递归死循环。
使用const关键字来防止拷贝构造函数内部对引用所指向内存空间内容的修改。
Q:为什么拷贝构造函数与构造函数函数名相同不会报错或重载覆盖?
C++中允许同一作用域函数名多意的情况存在,这称为函数重载。使用重载声明(函数名相同的声明)对一个函数进行重载,但他们的参数列表不能相同。在调用重载函数时,编译器会通过重载决策机制(将实参类型与形参类型相比较)来判断该使用的函数定义。而构造函数和拷贝构造函数满足函数重载的条件,这就解释了这个问题。
友元函数
友元函数是定义在类外部,但可以访问类私有成员和保护成员的一类函数。友元函数不属于类成员。使用关键字friend在类定义中声明友元函数。
class Box
{
double width{1};
double breadth{2};
double length{3};
public:
friend void printArea(Box box);
};
void printArea(Box box)
{
cout << box.width * box.breadth * box.length << endl;
}
也可定义某个类的友元类,这样友元类中的所有成员函数都可以访问这个类的私有成员和保护成员。
友元类的声明:
friend class AuxiBox;
静态成员
静态成员被类的所有对象共享,可以通过类名直接访问。在类中定义一个静态成员使用关键字static。
class Box
{
double length{1};
double width{2};
double height{3};
public:
static int num; //声明一个静态成员
Box();
};
Box::Box(double l=2.0, double b=2.0, double h=2.0)
{
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
num++;
}
int Box::num = 0; //初始化静态成员
定义上述代码中的静态成员后,每次创建一个类对象都会使计数器加1。
可以直接通过类名访问静态成员,当然可以使用对象访问。
//直接访问类静态成员
cout << Box::num << endl;
静态成员定义后需要初始化,若不进行初始化,在第一个实例化对象被创建时,会自动初始化为0。
需要注意的是我们无法在声明时为一个可变的静态成员初始化:
C++规定const静态类成员可以直接初始化,其他非const的静态类成员需要在类声明以外初
始化
类似的,可以通过定义静态成员函数来访问和使用静态成员。这类函数在没有创建类对象时也可被调用。
//在类中声明并定义
static getNum()
{
return num;
}
*******************
//类中声明,类外定义
Box::getNum()
{
return num;
}
注意静态成员函数只能访问全局变量和同类中静态数据成员,而不能访问其他的静态成员函数以及类外部的其他函数。
调用静态成员函数需要在函数名前加上类名:
cout << Box::getNum() << endl;
this指针
C++中的所有对象都可以通过this指针来访问自己的位置,this指针也是所有成员函数的隐藏参数。
class Box
{
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
public:
double getArea()
{
return length * breadth * height;
}
int compare(Box b)
{
return this->getArea() > b.getArea();
}
};
this指针是const指针,即在实例对象被创建后它的指向地址已经确定,不能再指向其他的地址。this指针的作用域被限制在对象内,只有成员函数可以使用。
友元函数和友元类不是成员函数,因此无法访问某个对象的this指针。
class与struct
在C++中,对C的struct关键字进行了扩充,使其不再局限于是一种数据结构。其功能与class的作用相当,可以使用struct关键字来定义类。
struct Box
{
int length;
int width;
int height;
int getArea();
}
使用方法与class相同,但也有一些差异:
- 默认的访问属性不同
struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
对于struct中定义的类成员,若想将其指定为私有,需要手动在声明中加上private。
- 默认的继承访问权限不同
默认的继承访问权限。struct是public的,class是private的。
使用struct定义的继承类,若不指定继承关系,则默认是public。
const成员函数
若类的成员函数不会修改数据成员,推荐将其声明为const类型,即常成员函数。
class Box
{
int length;
int width;
int height;
public:
Box(int a, int b, int c): length(a), width(b), height(c){}
void print() const //该函数不会修改数据成员
{
cout << length << width << height << endl;
}
};
对于声明是const类型的成员函数,如果函数内部出现修改数据成员的情况,编译器会指出错误。
放置在函数声明后面的const关键字实质上是对this指针的修饰,所有成员函数都会默认传入一个 Box *const this 的指针,声明为常成员函数后,this指向(对象)地址的内容无法被修改。