C++教程-----C++类和对象,一篇文章两小时带你理解清C++的类和对象

C++教程正在更新中,具体可以查看教程目录

1.什么是类

首先来看维基百科中对于C++的类的描述

C++程序设计允许程序员使用类(class)定义特定程序中的数据类型。这些数据类型的实例被称为对象,这些实例可以包含程序员定义的成员变量、常量、成员函数,以及重载的运算符。语法上,类似C中结构体(struct)的扩展,C中结构体不能包含函数以及重载的运算符。

C++中的类可以认为是对于C中结构体的增强版,在维基百科中对于C结构体和C++类的对比如下:

在 C++ 中,结构体 是由关键词 struct 定义的一种数据类型[1]。他的成员和基类默认为公有的(public)。由关键词 class 定义的成员和基类默认为私有的(private)。这是C++中结构体和类仅有的区别。

2.类的定义

类的定义需要使用关键字 class 开头,这里和C语言中的 struct 类似,接下来我们定义一个用来描述对象 box 的类,这个类描述了对象 box 的长度,宽度,和高度,如下所示:

class box{
	private:
		int height, lenth, breadth;
};

这里的 private 表示这里的类成员的访问属性,类中一共有三种访问属性,private,public,protected 三种访问属性。这个地方在稍后会讲解。

3.定义类的对象

类提供的对象的蓝图,所以对象是根据类来进行创建的,在定义一个类的对象和定义一个基本类型的变量的方式是一样的,如下所示:

	box box_1;
	box box_2;
	box box_1, box_2;

这里和C语言中对于结构体的定义类似,这里定义的对象同时具有对象中的成员变量,举例来讲,就是这里定义的 box_1 对象,其中包含三个变量 height,lenth,breadth

4.访问访问权限为 public 的数据成员

类的对象的公共数据成员可以通过直接成员访问运算符进行访问,前提是这个对象的数据成员是公共的,也就是 public 的,我们通过下面的代码来实现一下对类的对象的公共数据成员的访问:

class box{
	public:
		int height, lenth, breadth;
};

int main(){
	box my_box;
	scanf("%d%d%d", &my_box.lenth, &my_box.breadth, &my_box.height);
	int volume = my_box.lenth * my_box.breadth * my_box.height;
	printf("%d", volume);
	return 0;
}

通过上面这段代码,我们可以计算得出一个盒子的体积,前面提到了,这样类似于 struct 的访问方式仅适用于这个类的对象定义的成员变量的访问权限是 public 的,当访问权限为 private 或者 protected 时这样的访问方式就会出现问题。

5.类的成员函数

类的成员函数是指把函数的定义和原型都写在类的定义内部的函数,就像类定义中的其他变量一样,类的成员函数也是类的一个成员,这样的成员函数可以操作任意的对象,也可以访问对象中的成员。我们接下来用成员函数来访问类中的成员变量,而不是直接访问这些成员变量,如下所示:

class box{
	public:
		int height, lenth, breadth;
		int get_volume(){
			return height * breadth * height;
		} 
};

上面是将函数的主体定义在类的内部,同样函数的主体也可以定义在类的外面,定义的方式如下:

class box{
	public:
		int height, lenth, breadth;
		int get_volume();
};

inline int box::get_volume(){
	return height * lenth * breadth;
}

当成员函数在类的外部进行定义的时候,在 :: 运算符之前必须是类的名称。

在调用类的成员函数的时候,需要使用 . 运算符,和结构体的使用类似,具体操作方式如下:

class box{
	public:
		int height, lenth, breadth;
		int get_volume();
};

inline int box::get_volume(){
	return height * lenth * breadth;
}

int main(){
	box my_box;
	scanf("%d%d%d", &my_box.lenth, &my_box.breadth, &my_box.height);
	printf("%d", my_box.get_volume());
	return 0;
}

通过上面的方式同样可以通过成员函数间接得到这个盒子的体积。

我们接下来用上面学到的知识来给成员变量赋值并获得类中不同成员的值。

class box{
	public:
		int height, lenth, breadth;
		int get_volume();
		void set_lenth(int len){
			lenth = len;
		} 
		void set_breadth(int bre){
			breadth = bre;
		}
		void set_height(int he){
			height = he;
		}
};

inline int box::get_volume(){
	return height * lenth * breadth;
}

int main(){
	box my_box;
	my_box.set_lenth(1), my_box.set_breadth(2), my_box.set_height(3);
	printf("%d", my_box.get_volume());
	return 0;
}

通过上面的方式我们同样可以得到盒子的体积。

6.类的访问修饰符

数据封装是面向对象编程的一个重要的特点,将数据封装可以防止函数直接访问类内部成员,而确定类的访问限制主要是通过在类的主体中运用访问修饰符: public,private,protected 来确定的。一个类可以有很多个访问修饰符来标记区域,可以作用的区域是到下一个访问修饰符出现之前或者遇到这个类结束的右括号之前。

如果类中的成员没有声明访问权限,那么默认访问权限是 private 的。

public成员

public 的成员是在类的外部仍可进行访问的,在类的外部可以不使用任何成员函数来给成员赋值和或者变量的值。如下所示

class box{
	public:
		int height, lenth, breadth;
		void set_lenth(int len){
			lenth = len;
		} 
		void set_breadth(int bre){
			breadth = bre;
		}
		void set_height(int he){
			height = he;
		}
};

int main(){
	box my_box;
	my_box.set_lenth(1), my_box.set_breadth(2), my_box.set_height(3);
	printf("%d%d%d", my_box.lenth, my_box.breadth, my_box.height);
	return 0;
}

通过上面的代码由于变量是 public 的,我们可以直接在类的外部访问成员变量的值,同样在类的外部也可以直接设置成员变量的值。

private成员

private的成员变量或函数在类外面的是无法访问的,甚至无法查看,只有类和友元可以访问私有成员。在默认的情况下,类中定义的所有成员都是私有的,如果需要对私有的成员变量或函数进行访问,需要运用类中的成员函数进行访问。如下所示:

class box{
	int volume;
	private:
		int height, lenth, breadth;
	public:
		int get_volume();
		void set_lenth(int len){
			lenth = len;
		} 
		void set_breadth(int bre){
			breadth = bre;
		}
		void set_height(int he){
			height = he;
		}
};

inline int box::get_volume(){
	volume = height * lenth * breadth;
	return volume;
}

int main(){
	box my_box;
	my_box.set_lenth(1), my_box.set_breadth(2), my_box.set_height(3);
	printf("%d", my_box.get_volume());
	return 0;
}

通过上面的函数我们同样能获得盒子的体积,由于类中的成员变量没有指定访问权限,所以C++默认变量 volume 的访问权限是 private 的。

protected成员

protected 的成员变量和函数和 private 的成员类似,但是 protected 的成员在派生类 / 子类中是可以访问的。至于派生和继承在之后将会提到,在下面的代码中,我们从父类 box 中派生出了子类 small_box 如下所示:

class box{
	protected:
		int volume;
		int height, lenth, breadth;
	public:
		void set_lenth(int len){
			lenth = len;
		} 
		void set_breadth(int bre){
			breadth = bre;
		}
		void set_height(int he){
			height = he;
		}
};
class small_box : public box{
	public:
		int get_volume();
};

inline int small_box::get_volume(){
	volume = height * lenth * breadth;
	return volume;
}

int main(){
	small_box my_box;
	my_box.box::set_lenth(1), my_box.box::set_breadth(2), my_box.box::set_height(3);
	printf("%d", my_box.get_volume());
	return 0;
}

我们定义了基类 box 和派生类 small_box 通过派生类我们访问了被保护的成员变量并将这个成员变量进行输出。

继承中的特点

在类的继承中,有 public,protected,private 三种继承方式,他们相应的改变了基类成员的访问属性。

继承方式基类的public成员基类的protected成员基类的private成员继承引起的访问控制关系变化概括
public继承仍为public成员仍为protected成员不可见基类的非私有成员在子类的访问属性不变
protected继承变为protected成员变为protected成员不可见基类的非私有成员都为子类的保护成员
private继承变为private成员变为private成员不可见基类中非私有成员都成为子类的私有成员

但是无论上述的哪种继承方式下面的两点都不变:

​ 1.private 成员只能被本类(类内)成员和友元访问,不能被派生类访问

​ 2.protected 成员只能被派生类访问

public继承

class A{
	public:
		int a1;
	protected:
		int a2;
	private:
		int a3; 
	public:
		int a;
		A(){
			a1 = 1, a2 = 2, a3 = 3, a = 4;
		}
		void function(){
			printf("%d %d %d %d\n", a1, a2, a3, a);
		}
};
class B : public A{
	public:
		int a;
		B(int i){
			A();
			a = i;
		}
		void function(){
			cout << a << " "; //正确,public成员 
			cout << a1 << " ";//正确,基类的public成员,在派生类中仍是public 
			cout << a2 << " ";//正确,基类的protected成员在派生类中可以被访问 
			cout << a3 << " ";//错误,基类的private成员在派生类中无法访问 
		}
};

int main(){
	B b(10);
	cout << b.a << " ";//正确,派生类中的public成员 
	cout << b.a1 << " ";//正确,基类的public成员 
	b.function();//正确,派生类的pbulic成员函数 
	b.A::function();//正确,基类的pbulic成员 
	cout << b.a2 << " ";//错误, protected成员不能在类外被访问 
	cout << b.a3 << " ";//错误, private成员不能在类外被访问 
	return 0;
}

protected继承

class A{
	public:
		int a1;
	protected:
		int a2;
	private:
		int a3; 
	public:
		int a;
		A(){
			a1 = 1, a2 = 2, a3 = 3, a = 4;
		}
		void function(){
			printf("%d %d %d %d\n", a1, a2, a3, a);
		}
};
class B : protected A{
	public:
		int a;
		B(int i){
			A();
			a = i;
		}
		void function(){
			cout << a << " "; //正确,public成员 
			cout << a1 << " ";//正确,基类的public成员,变为protected可以被派生类访问 
			cout << a2 << " ";//正确,基类的protected成员在派生类中仍然是protected成员可以被访问 
			cout << a3 << " ";//错误,基类的private成员在派生类中无法访问 
		}
};

int main(){
	B b(10);
	cout << b.a << " ";//正确,派生类中的public成员 
	cout << b.a1 << " ";//错误,基类的public成员变为protected成员在类外无法被访问 
	b.function();//正确,派生类的pbulic成员函数 
	b.A::function();//错误,基类的pbulic成员变为protected成员无法在类外访问 
	cout << b.a2 << " ";//错误, protected成员不能在类外被访问 
	cout << b.a3 << " ";//错误, private成员不能在类外被访问 
	return 0;
}

private继承

class A{
	public:
		int a1;
	protected:
		int a2;
	private:
		int a3; 
	public:
		int a;
		A(){
			a1 = 1, a2 = 2, a3 = 3, a = 4;
		}
		void function(){
			printf("%d %d %d %d\n", a1, a2, a3, a);
		}
};
class B : private A{
	public:
		int a;
		B(int i){
			A();
			a = i;
		}
		void function(){
			cout << a << " "; //正确,public成员 
			cout << a1 << " ";//正确,基类的public成员,变为private成员可以被派生类访问 
			cout << a2 << " ";//正确,基类的protected成员在派生类中变为private成员可以被访问 
			cout << a3 << " ";//错误,基类的private成员在派生类中无法访问 
		}
};

int main(){
	B b(10);
	cout << b.a << " ";//正确,派生类中的public成员 
	cout << b.a1 << " ";//错误,基类的public成员变为private成员在类外无法被访问 
	b.function();//正确,派生类的pbulic成员函数 
	b.A::function();//错误,基类的pbulic成员变为private成员无法在类外访问 
	cout << b.a2 << " ";//错误, private成员不能在类外被访问 
	cout << b.a3 << " ";//错误, private成员不能在类外被访问 
	return 0;
}

7.构造函数和析构函数

类的构造函数

类的构造函数是类的一种特殊的成员函数,这个函数会在每次创建类的新的对象的时候进行调用,构造函数的名称和类的名称是完全相同的,并且函数在调用的时候不会返回任何类型,也不会返回 void ,构造函数之所以名为构造函数,可以用于为某些成员变量设置初始值,类的构造函数如果我们不明确写出是不会出现在类中的,当对象被创造的时候会自动生成一个构造函数,我们通过如下所示的代码来理解构造函数:

class box{
	private:
		int length, breadth, height;
	public:
		box();//构造函数 
		void get_box_volume(int l, int b, int h);
};

box :: box(){
	printf("This object is being created!\n");//当这个对象被定义的时候输出
}
void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	printf("%d", length * breadth * height);
}

int main(){
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	box my_box;
	my_box.get_box_volume(l, b, h);
	return 0;
}

带参数的构造函数

C++中默认的构造函数没有任何的参数但是如果程序需要的情况下构造函数也可以带有参数,这样就可以在创建对象的时候给对象赋初值(在C++11的新标准中在定义变量的时候可以直接给变量赋初值,但是在较低标准的C++中新的变量是无法赋初值的)通过下面的代码我们来理解带参数的析构函数为对象的变量赋初值:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		box(int v);//构造函数 
		void get_box_volume(int l, int b, int h);
};

box :: box(int v){
	volume = v;
	printf("This object is being created, volume = %d!\n", volume);
}
void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
	printf("%d", volume);
}

int main(){
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	box my_box(0);
	my_box.get_box_volume(l, b, h);
	return 0;
}

使用初始化列表类初始化字段

我们还可以用初始化列表来初始化字段,我们看看下面的代码:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		box(int v);//构造函数 
		void get_box_volume(int l, int b, int h);
};

/*box :: box(int v){
	volume = v;
	printf("This object is being created, volume = %d!\n", volume);
}*/
box :: box(int v): volume(v){
	printf("This object is being created, volume = %d!\n", volume);
}
void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
	printf("%d", volume);
}

对于构造函数这两种语法是相同的,同样这样的方法也可以初始化多个变量,可以参考以下代码:

Class :: Class(int a, int b, int c) : object1(a), object2(b), object3(c){
	...
} 

类的析构函数

类的析构函数是类的一种特殊的成员函数,他会在每个删除所创建的对象的时候会调用这个函数,析构函数的的名称和类的名称是完全相同的,只是在这个函数名前面加个 ‘~’ 作为前缀,和构造函数一样,这个函数不会返回任何值,也不能带有任何参数,析构函数有助于跳出程序(关闭文件,释放内存)前得释放资源。

我们来看下面的代码来理解析构函数:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		box();//构造函数 
		void get_box_volume(int l, int b, int h);
		~box();
};

box :: box(){
	volume = 0;
	printf("This object is being created, volume = %d!\n", volume);
}
void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
	printf("%d\n", volume);
}
box :: ~box(){
	printf("This object is being deleted!");
}

int main(){
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	box my_box;
	my_box.get_box_volume(l, b, h);
	return 0;
}

这就是析构函数的使用方式。

每个对象调用析构函数的时候,顺序是和定义对象相反的,构造函数和析构函数的调用顺序如下


Class object1, objdect2, object3;
调用顺序:
Class object1
	|
	v
Class object2 
	|
	v
Class object3 
	|
	v
~Class object3
	|
	v
~Class object2
	|
	v
~Class object1 

8.拷贝构造函数

如果我们需要将函数的全部变量赋值给另一个变量,就需要引进新的一类函数——拷贝构造函数

拷贝构造函数是一类特殊的构造函数,在创建对象的时候,是使用同一类之前创建的对象类初始化新创建的对象,我们接下来看看拷贝构造函数的适用条件:

​ 1.通过使用同一个同类型的对象类初始化新创建的对象

​ 2.复制对象把它作为参数传递给函数

​ 3.复制对象,并从函数返回这个对象

如果在类中没有定义拷贝构造函数,C++的编译器会自动生成一个拷贝构造函数。但是如果这个类带有指针变量,并且有动态内存分配,那么它必须要有一个拷贝构造函数。我们看一下拷贝构造函数的常见形式:

Class(const Class &object){
	...//构造函数的主体 
}

这里的 object 是一个对象的引用,这个对象是用来初始化另一个对象的,我们通过下面的代码来理解拷贝构造函数的使用:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		box();//¹¹Ô캯Êý 
		void get_box_volume(int l, int b, int h);
		~box();
		box(const box &my_box); 
};

box :: box(){
	volume = 0;
	printf("This object is being created, volume = %d!\n", volume);
}
void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
	printf("%d\n", volume);
}
box :: ~box(){
	printf("This object is being deleted!\n");
}
box :: box(const box &my_box){
	length = my_box.length, breadth = my_box.breadth, height = my_box.height, volume = my_box.volume;
	printf("This object is being copyed!\n");
	volume = length * breadth * height;
	printf("%d\n", volume);
}

int main(){
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	box my_box;
	my_box.get_box_volume(l, b, h);
	box my_box1(my_box);
	return 0;
}

上面是拷贝构造函数的定义,通过拷贝构造函数可以复制对象的内容。

在维基百科上介绍了一些拷贝构造函数的其他内容:

复制构造函数(英语:Copy constructor)是C++编程语言中的一种特别的构造函数,习惯上用来创建一个全新的对象,这个全新的对象相当于已存在对象的副本。这个构造函数只有一个参数(引数):就是用来复制对象的引用(常用const修饰)。构造函数也可以有更多的参数,但除了最左第一个参数是该类的引用类型外,其它参数必须有默认值。

类的复制构造函数原型通常如下:Class_name(const Class_name & src);

一般来说,假如程序员没有自行编写复制构造函数,那么编译器会自动地替每一个类创建一个复制构造函数;相反地,程序员有自行编写复制构造函数,那么编译器就不会创建它。

当对象包括指针或是不可分享的引用时,程序员编写显式的复制构造函数是有其必要性的,例如处理文件的部分,除了复制构造函数之外,应该还要再编写析构函数与赋值运算符的部分,也就是三法则。

下述代码编译时报错。因为复制构造函数的参数如果是传值,将导致对自身无限递归。

class X;
X(X copy_from_me);
X(const X copy_from_me);
调用复制构造函数的情形包括:

用一个对象来初始化正在构造的对象变量;
函数返回一个对象;
函数参数作为对象传值;
抛出一个异常对象;
捕捉一个异常对象;
对象放在大括号中,即{ }。
上述情形未必会调用复制构造函数。因为C++标准允许编译器实现做一些优化。例如:

Class X b=X();
Microsoft Visual C++ 2010编译器仅执行了一次缺省构造函数,没有执行复制构造函数。

上面的内容仅供参考。

9.友元函数

前面介绍了C++中类对对象进行了封装,但是如果想访问封装的数据成员就需要用到友元函数。

维基百科中对友元函数进行了介绍:

在面向对象编程中,友元函数(friend function)是一个指定类(class)的“朋友”,该函数被允许访问该类中private、protected、public的数据成员。普通的函数并不能访问这些数据,然而宣告一个函数成为一个类的友元函数则被允许访问这些数据。

友元函数的宣告可以放在类声明的任何地方,不受访问限定关键字private、protected、public的限制。一个相似的概念是友谊类。

友谊关键字应该谨慎使用。如果一个拥有private或者protected成员的类,宣告过多的友元函数,可能会降低封装性的价值,也可能对整个设计框架产生影响。

类的友元函数是定义在类的外部,但是有权访问类中的 private 成员和 protected 成员,尽管友元函数有在类的定义中出现过,但是友元函数不是类的成员函数。

友元可以是一个函数,这个函数被称为友元函数,同样友元也可以是一个类,这个类被称为友元类,在这种情况下,整个类及其成员都是友元。

如果要声明函数为一个类的友元,需要在类的定义中这个函数原型前面使用关键字 friend 代码如下所示:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		box();//构造函数 
		friend void get_box_volume(int l, int b, int h);//友元函数 
		~box();
		box(const box &my_box); 
};

声明一个类 Class_2 的所有成员为类 Class_1 的友元,需要在类 Class_1 的定义中进行如下的声明:

friend class Class_2

我们接下来用友元函数来尝试访问类中的变量,并将变量的值进行输出,代码如下:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		void get_box_volume(int l,int b, int h);
		friend void print_box_volume(box my_box);//友元函数 
};

void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
}
void print_box_volume(box my_box){
	printf("%d\n", my_box.volume);
}

int main(){
	box my_box;
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	my_box.get_box_volume(l, b, h);
	print_box_volume(my_box);
	return 0;
}

接下来我们用友元类同样来实现上面获得体积的功能,代码如下:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		void get_box_volume(int l,int b, int h);
		friend class friend_box;
};
class friend_box{
	public:
		void print_box_volume(box my_box){
			printf("%d", my_box.volume);
		}	
};

void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
}


int main(){
	box my_box;
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	my_box.get_box_volume(l, b, h);
	friend_box my_box_2; 
	my_box_2.print_box_volume(my_box);
	return 0;
}

10.内联函数

在维基百科中对内联函数有较为详细的解释,摘录如下:

在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。另外还需要特别注意的是对递归函数的内联扩展可能引起部分编译器的无穷编译。
设计内联函数的动机

内联扩展是一种特别的用于消除调用函数时所造成的固有的时间消耗方法。一般用于能够快速执行的函数,因为在这种情况下函数调用的时间消耗显得更为突出。这种方法对于很小的函数也有空间上的益处,并且它也使得一些其他的优化成为可能。

没有了内联函式,程式员难以控制哪些函数内联哪些不内联;由编译器自行决定是否内联。加上这种控制维度准许特定于应用的知识,诸如执行函式的频繁程度,被利用于选择哪些函数要内联。

此外,在一些语言中,内联函数与编译模型联系紧密:如在C++中,有必要在每个使用它的模块中定义一个内联函数;与之相对应的,普通函数必须定义在单个模块中。这使得模块编译独立于其他的模块。

与宏的比较
通常,在C语言中,内联展开的功能由带参宏(Macros)在源码级实现。内联提供了几个更好的方法:

宏调用并不执行类型检查,甚至连正常参数也不检查,但是函数调用却要检查。
C语言的宏使用的是文本替换,可能导致无法预料的后果,因为需要重新计算参数和操作顺序。

在宏中的编译错误很难发现,因为它们引用的是扩展的代码,而不是程序员键入的。
许多结构体使用宏或者使用不同的语法来表达很难理解。内联函数使用与普通函数相同的语言,可以随意的内联和不内联。

内联代码的调试信息通常比扩展的宏代码更有用。

语言支持

C++,C99. C11和GNU C都支持内联函数,然而1989 ANSI C,这个最被广泛使用的C标准却不支持。在Ada中,关键字“pragma”可以用来声明内联。其他的大部分编程语言,包括Java和函数式语言,不支持内联函数,但他们的编译器常常进行强制性的内联扩展。不同的编译器在内联扩展上有处理不同复杂程度函数的能力。主流的C++编译器如Visual C++和GCC提供了一个选项来自动内联任何一个合适的函数,即使它们没有被声明为内联函数。

内联函数在C++中的写法如下:

inline int max (int a, int b)
{
if (a > b)
return a;
else
return b;
}
a = max (x, y); // 等价于 “a = (x > y ? x : y);”

内联函数的不足
除了通常使用内联扩展可能带来的问题,作为一种编程语言特性的内联函数也可能并没有看起来那么有效,原因如下:

通常,编译器比程序设计者更清楚对于一个特定的函数是否合适进行内联扩展;一些情况下,对于程序员指定的某些内联函数,编译器可能更倾向于不使用内联甚至根本无法完成内联。
对于一些开发中的函数,它们可能从原来的不适合内联扩展变得适合或者倒过来。尽管内联函数或者非内联函数的转换易于宏的转换,但增加的维护开支还是使得它的优点显得更不突出了。

对于基于C的编译系统,内联函数的使用可能大大增加编译时间,因为每个调用该函数的地方都需要替换成函数体,代码量的增加也同时带来了潜在的编译时间的增加。

C++的内联函数通常和类一起使用,如果说一个函数是内联的,那么代码在编译的时候,编译器会把这个函数的代码副本放置在每个调用这个函数的地方,因此如果对内联函数进行修改就需要重新编译整个客户端,因为编译器需要重新将所有代码重新更换一次,否则仍然会继续使用旧的函数。使用内联函数可以让程序运行速度变得更快,但是由于内联函数使用时会占用系统的内存,同时也会减慢编译时间,这个也是因为在编译的时候需要将内联函数的代码放在调用函数的地方来加快程序运行时的速度,这样显然会使代码编译的时间边长。因此,在使用内联函数的时候需要权衡加快代码运行速度和编译时间,占用内存之间的矛盾。

如果需要将一个函数变为内联函数,需要在定义函数的最前面放置关键词 inline。

在类中定义的函数都是内联函数,即使没有使用 inline 关键字。

下面的代码将 max 函数定义为内联函数,如下是内联函数的使用方法:

inline int max(int number_1, int number_2){
	return number_1 > number_2 ? number_1 : number_2; 
}

int main(){
	int number_1, number_2;
	scanf("%d%d", &number_1, &number_2);
	printf("%d", max(number_1, number_2));
	return 0; 
}

引入内联函数是为了解决程序中函数调用的效率问题,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于非内联函数,都是在函数运行的时候才会被替代,这里使用内联函数就是明显的用空间换时间的一种做法,所以内联函数通常是 10 行以内的小函数,在使用内联函数的时候需要注意如下几点:

​ 1.在内联函数内不允许使用循环和开关语句

​ 2.内联函数的定义必须出现在内联函数的第一次调用之前

​ 3.类中的函数都是内联函数

使用内联函数有一定的优缺点,总结如下:

优点:当函数体在比较小的时候,内联函数可以让代码更加高效,对于存取函数和其他函数体比较小,性能比较关键的函数,使用内联函数式有明显优势的

缺点:滥用内联函数将导致程序运行速度变慢,内联函数的使用可能使代码量增加或者减少,这取决于内联函数的长度,如果内联函数比较短小,那么使用内联函数将使得代码的成都变小,但是如果内联一个相当大的函数将导致函数的长度变长,现在的处理器由于更好的利用了指令缓存,小巧的代码经常可以更快的执行

那么我们通常不要内联超过十行的函数,并且需要谨慎对待析构函数,因为析构函数常常比表面看起来的长度更长,因为可能有隐含的成员和基类的析构函数被调用。同时尽量不要内联包含循环和 switch 的语句,如果内联这样的语句通常情况下会得不偿失(除非在大多数情况下,这些循环或者 switch 语句基本不会被执行)

并且,有些函数即使被声明为内联函数也不一定会被编译器内联,比如虚函数和递归函数就不会被正常内联。通常,递归函数不应该被声明为内联函数(因为递归调用堆栈的展开并不像循环那么简单,比如递归的时候递归层数使未知的,因此大多数的编译器并不支持内联递归函数)。而虚函数内联的主要原因使想把函数体放在类定义里面,或者是当作文档描述行为,比如精简短小的存取函数。

在维基百科中是这样描述虚函数的:

在面向对象程序设计领域,C++、Object Pascal 等语言中有虚函数(英语:virtual function)或虚方法(英语:virtual method)的概念。这种函数或方法可以被子类继承和覆盖,通常使用动态调度实现。这一概念是面向对象程序设计中(运行时)多态的重要组成部分。简言之,虚函数可以给出目标函数的定义,但该目标的具体指向在编译期可能无法确定。

虚函数在设计模式方面扮演重要角色。例如,《设计模式》一书中提到的23种设计模式中,仅5个对象创建模式就有4个用到了虚函数(抽象工厂、工厂方法、生成器、原型),只有单例没有用到。

11.this指针

在C++中每一个对象都能通过 this 指针访问自己的地址,this 指针是所有成员函数的隐含参数,因此在成员函数的内部,this 指针可以用来指向调用的对象,但是友元函数使没有 this 指针的,因为友元函数的并不是类的成员函数。因此,只有成员函数才拥有 this 指针,我们接下来用 this 指针访问变量来比较两个盒子题解的大小,代码如下:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		void get_box_volume(int l,int b, int h);
		bool compare_box_volume(box this_box);
};

void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
}
bool box :: compare_box_volume(box this_box){
	if(this -> volume > this_box.volume){
		return 1;
	}
	return 0;
}

int main(){
	box my_box_1, my_box_2;
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	my_box_1.get_box_volume(l, b, h);
	scanf("%d%d%d", &l, &b, &h);
	my_box_2.get_box_volume(l, b, h);
	if(my_box_1.compare_box_volume(my_box_2)){
		printf("The frist box is larger than the second box");
	}
	else{
		printf("The first box is equal or smaller than the second box");
	}
	return 0;
}

当我们调用成员函数的时候,实际上使替代某个对象来调用它,成员函数通过 this 指针,这个额外的隐式参数来访问调用他的那个对象,当我们调用一个成员函数的时候,用请求这个函数的对象地址初始化 this 。在成员函数的内部,我们可以直接调用这个函数对象的成员,并且不需要通过成员访问运算符来做这一点,因为 this 指针指向的就是这个对象。所有对类的成员的访问都可以看作是使用 this 隐式指针对其的引用,this 形参使隐式定义的,任何定义名为 this 的参数或者是变量的行为都是非法的,this 指针的目的使指向这个指针而不是改变这个指针的地址(当然也不允许改变这个指针指向的地址),因此 this 指针是一个常量指针

12.指向类的指针

一个指向类的指针和指向结构的指针类似,访问指向类的指针的成员常需要运算符 -> 和访问只想结构的指针一样,在使用指针之前需要对指针进行初始化,我们通过下面的代码来理解指向类的指针的用法:

class box{
	private:
		int length, breadth, height;
		int volume;
	public:
		void get_box_volume(int l,int b, int h);
		bool compare_box_volume(box this_box);
		void print_box_volume();
};

void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
}
void box :: print_box_volume(){
	printf("The volume of this box is: %d\n", this -> volume);
}

int main(){
	box my_box_1, my_box_2;
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	my_box_1.get_box_volume(l, b, h);
	scanf("%d%d%d", &l, &b, &h);
	my_box_2.get_box_volume(l, b, h);
	box *ptr;//定义一个box类的指针
	ptr = &my_box_1;//指针指向对象my_box_1的地址
	ptr -> print_box_volume();//通过指针访问这个对象的成员函数
	ptr = &my_box_2;//将指针指向对象my_box_2的地址
	ptr -> print_box_volume(); //通过指针访问这个对象的成员函数
	return 0;
}

13.类的静态成员

假如说我们需要一个变量来统计创建的对象的数量,这个变量在每个对象中使公共的,都可以修改和使用,值不因对象的创建的和删除而改变,这是我们就需要用到静态成员。

我们可以使用 static 关键字来把成员定义为静态的这意味着无论我们创建多少个类的对象,这个静态成员变量都只有一个副本,也就是说这个变量不会因为该类的对象的数目的增加而增加,而是只保持一个变量。

静态成员在这个类的所有的对象之间是共享的,如果不存在其他的初始化语句的时候,在创建第一个对象的时候,所有的静态成员的数据都会被初始化为 0 。我们不能将静态成员的初始化语句放在类的定义之中,但是可以在类外来声明静态变量从而对静态变量进行初始化。

通过下面的代码来理解静态成员:

class box{
	private:
		int length, breadth, height;
		int volume;
		static int count_object;//静态成员变量
	public:
		void get_box_volume(int l,int b, int h);
		bool compare_box_volume(box this_box);
		void print_box_volume();
		box(int l = 0, int b = 0, int h = 0) : length(l), breadth(b), height(h){
			++ count_object;
			printf("Total object : %d\n", count_object);
		}
};
int box :: count_object = 0;

void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
}
void box :: print_box_volume(){
	printf("The volume of this box is: %d\n", this -> volume);
}

int main(){
	box my_box_1, my_box_2;
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	my_box_1.get_box_volume(l, b, h);
	scanf("%d%d%d", &l, &b, &h);
	my_box_2.get_box_volume(l, b, h);
	box *ptr;
	ptr = &my_box_1;
	ptr -> print_box_volume();
	ptr = &my_box_2;
	ptr -> print_box_volume(); 
	return 0;
}

上面的代码我们定义了一个静态成员变量来统计现在在 box 类的对象的个数,当对象被创造在调用构造函数的时候,让静态成员变量 count_object 的值增加 1 ,通过这样的方式来统计程序内对象的数量

静态成员函数

上面讲到了变量可以被声明为静态的,同样成员函数也可以被声明为静态的,也就是静态成员函数。如果将成员函数声明为静态的,就可以将函数和类的任何特定的对象独立开,静态成员函数即使在不存在类的对象的情况下仍能使用,静态成员函数只要使用类名加上 :: 就可以被访问。

静态成员函数只能访问静态成员数据,其他静态成员函数和类之外的函数

静态成员函数不能访问类的 this 指针,可以使用静态成员函数类判断类的某些对象是否已经被创建

通过下面的代码来看一下如何使用静态成员函数:

class box{
	private:
		int length, breadth, height;
		int volume;
		static int count_object;
	public:
		void get_box_volume(int l,int b, int h);
		bool compare_box_volume(box this_box);
		void print_box_volume();
		box(int l = 0, int b = 0, int h = 0) : length(l), breadth(b), height(h){
			++ count_object;
		}
		static void get_count_object(){
			printf("Total object : %d\n", count_object);
		}
};
int box :: count_object = 0;

void box :: get_box_volume(int l, int b, int h){
	length = l, breadth = b, height = h;
	volume = length * breadth * height;
}
void box :: print_box_volume(){
	printf("The volume of this box is: %d\n", this -> volume);
}

int main(){
	box :: get_count_object();
	box my_box_1;
	box :: get_count_object();
	box my_box_2;
	box :: get_count_object();
	int l, b, h;
	scanf("%d%d%d", &l, &b, &h);
	my_box_1.get_box_volume(l, b, h);
	scanf("%d%d%d", &l, &b, &h);
	my_box_2.get_box_volume(l, b, h);
	box *ptr;
	ptr = &my_box_1;
	ptr -> print_box_volume();
	ptr = &my_box_2;
	ptr -> print_box_volume(); 
	return 0;
}

通过上面的代码我们通过静态成员函数获得了属于 box 类的对象的数目

静态成员变量在类中仅仅是声明,没有被定义,实际上静态成员变量在类的外面给被境地,在类内的定义实际上是在给静态成员变量分配内存,如果在类外不加定义程序就会报错。所谓的初始化是给变量赋一个初始值,而定义则是给变量分配内存

也就是在定义静态成员变量时在类外必须加上:

T/*(类型)*/ calss :: volume;//具体的值可以不加

可以理解成在类内定义就是在类内声明这个类中有这样一个静态成员变量,但是实际上这个静态成员变量是在类外定义的,如果定义时没有给这个静态成员变量赋初值的话这个变量的值默认为 0

通过静态成员变量可以清楚的了解构造函数和析构函数的调用情况,也就是想上面代码那样,通过输出现存的这个类的对象的数量来判断构造函数和析构函数的调用时机,同样通过静态成员变量也能得知关于每个对象的构造函数的调用顺序和析构函数的调用顺序是相反的,也就是最先生成的对象最先调用构造函数,在程序结束时最后调用析构函数

在此总结类中的特殊的成员变量的初始化问题:

​ 1.常量变量:必须通过构造函数的参数列表进行初始化

​ 2.引用变量:必须通过构造函数的参数列表进行初始化

​ 3.普通静态变量:在类外进行定义和初始化

​ 4.静态整型常量:可以在定义的时候进行初始化

​ 5.静态非整形常量:不能再定义的时候进行初始化,需要在类外通过 :: 初始化

查看上一篇:C++指针(上)-----基础篇
查看下一篇:C++继承,一篇文章一小时带你理解清C++的继承
查看目录:C++教程目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值