zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++类与对象(一)

2023-06-13 09:17:43 时间

类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。

#include <iostream>
using namespace std;
struct N1
{
	//成员函数
	int add(int x,int y)
	{
		return x + y;
	}
	//成员变量
	int a;
	double b;
};
int main()
{
	struct N1 s;//在C++当中前面不加struct也是可以的
	int sum = s.add(10, 20);
	cout << sum << endl;
	return 0;
}

上面结构体的定义,在C++中更喜欢用class来代替。 在类中定义的函数可以直接使用成员变量。

class N2
{
public:
	void equal_to(int c)
	{
		a = c;//万一这里的参数是a,参数的a想赋值给成员a,可读性很差
	}
private:
	int a;
};

那么,在成员命名的时候,我们最好在前面加上一个_用来区分,成员变量和其他的变量。

类的定义

class className
{
	// 类体:由成员函数和成员变量组成
};

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。 类的两种定义方式:

  1. 声明和定义全部放在类体中 成员函数如果在类中定义,这种情况下,编译器可能会将其当成内联函数。
#include <iostream>
#include "test.h"
using namespace std;
class N1
{
public://共有,都可以访问
	int add(int x, int y)
	{
		return x + y;
	}
private://私有,只能在类内部进行访问
	int _a;
	double _b;
};
int main()
{
	N1 s;
	int sum = s.add(10, 20);
	cout << sum << endl;
	return 0;
}
  1. 声明和定义在不同的文件中

定义和声明分离是为了更好的看到类的成员有什么。 注意:成员函数名前需要加类名:: 这里我分三个文件,一个头文件和两个源文件

//test.h
#include <iostream>
class N1
{
public://共有,都可以访问
	int add(int x, int y);
private://私有,只能在类内部进行访问
	int _a;
	double _b;
};
//test.cpp
#include "test.h"
int N1::add(int x, int y)
{
	return x + y;
}
#include "test.h"
using namespace std;
int main()
{
	N1 s;
	int sum = s.add(10, 20);
	cout << sum << endl;
	return 0;
}

声明和定义分开的时候如果有两个相同的成员函数定义,那么会有冲突,所以就需要说明是哪个作用域的。 然后成员函数中如果需要用到成员变量,就会先在局部变量里面寻找,没有就去类里面找,还没有就在全局找。

类的访问限定符及封装

访问限定符

访问限定符有三个:

public(共有) protected(保护) private(私有)

访问限定符是为了保护类里面的成员,防止在外部进行改动,比如说你在类中定义的成员变量只想通过类中定义的函数来进行更改,而不是在外部被更改。

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } 即类结束。
  4. class的默认访问权限为private,struct为public(因为struct要兼容C)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

#include <iostream>
using namespace std;
class N1
{
public://共有,都可以访问
	int add(int x, int y)
	{
		return x + y;
	}
private://私有,只能在类内部进行访问
	int a;
};
int main()
{
	N1 s;
	int sum = s.add(10, 20);
	s.a = 0;
	cout << sum << endl;
	return 0;
}

编译器这里就会报错。

注意,类的定义是一个声明。

封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。 封装本质上是一种管理,让用户更方便使用类。 在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域。 就比如上面声明和定义分离的例子,如果有相同名字的成员函数定义(不是重载函数),那么就要说明这两个成员函数是属于哪个域的。

某个域::某个成员。

运行没任何问题。

类的实例化

用类类型创建对象的过程,称为类的实例化。

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没 有分配实际的内存空间来存储它。 创建一个类就像盖房子你只是有了图纸,并没有具体的房屋,因为你还没有建筑,所以我们需要创建对象,就等于用这个图纸搞了一个房子,房子创建好之后才有实际空间。
  1. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。
#include <iostream>
using namespace std;
class N1
{
public:
	int a;
};
int main()
{
	N1 s1;
	N1 s2;
	N1 s3;
	s1.a;
	s2.a;
	s3.a;
}

类的对象大小的计算

计算类对象的大小

#include <iostream>
using namespace std;
class N1
{
public:
	int add(int x, int y)
	{
		return x + y;
	}
private:
	int a;
	int b;
};
int main()
{
	N1 s;
	cout << sizeof(N1) << endl;//也能计算类的大小,就像建房子的图纸上面有标注各个地方的大小一样
	cout << sizeof(s) << endl;
}

看起来和C语言的结构体内存是一样的,但是C++的类中有成员函数,这里貌似没有将成员函数的内存计算在内。

类对象的存储方式

在C++设计的时候,考虑过类如果储存成员函数,那么创建多个对象的时候会非常的占用内存,导致栈溢出,就像造一个小区,每家每户没必要都有健身器材,只要在特定地点建造健身器材就好了,大家一起用。 所以,在C++中,类的函数放在了公共代码区,编译器会自动在这里找,无论是你定义的哪个类都会在这里找。

其他的成员变量用C语言的结构体内存对齐就可以了。 在这里:结构体内存对齐 那么,如果类里面只有成员函数,没有成员变量,或者是什么都没有,那么长度是多少呢?

这里会占用一个字节,因为需要占位,我们说创建一个对象就等于按照图纸盖一个房子,但是不可能创建出来一个无,不然里面的成员函数和这个对象都没有存在的必要了。

类成员函数的this指针

C++这个this指针特性可以说是非常的方便。

引出

#include <iostream>
using namespace std;
class N1
{
public:
	void initialize(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	N1 s1;
	s1.initialize(2022, 10, 9);
	N1 s2;
	s2.initialize(2022, 10, 10);
	s1.print();
	s2.print();
}

可以看到,我们定义了两个同类型的对象,初始化时不同的参数,在调用不同对象的成员函数结果却不同,成员函数用的确实是同一个函数,至于为什么通过不同的对象调用这个函数的结果不同,那是因为有一个隐藏的this指针。

特性

类中每个成员函数都有一个隐藏的this指针,大概是这个样子的。

void print(N1 *const this)
{
	cout << this->_year << ' ' << this->_month << ' ' << this->_day << endl;
}

你在调用对象中print函数的时候会将这个对象的地址当作参数传过去。

这也说明了一个问题,如果上面不去定义一个对象而直接通过类访问成员函数,那么会无法传参给this指针。 平时this指针不用写,编译器已经帮你处理了。 C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。 注意,this指针存在于栈。(因为是形参) 但是每个编译器也不同,VS编译器是用寄存器(ecx)进行操作。

this指针可以为空吗?

代码1

#include <iostream>
using namespace std;
class N1
{
public:
	void print()//这里只接受了传过来的空指针
	{
		cout << "abc" << endl;//并没有解引用
	}
private:
	int _a;
};
int main()
{
	N1* p1 = nullptr;
	p1->print();//这里没有解引用,因为成员函数的地址不在对象中,在公共代码区域
	return 0;
}

这段代码是能运行成功的,我们知道空指针解引用是会报错的,但是这里并没有解引用。 this指针接收的是对象的地址,这里的p是对象的指针,所以直接传过去就可以了。

代码2

#include <iostream>
using namespace std;
class N2
{
public:
	void print()
	{
		cout << _a << endl;//这段代码就把this解引用了,this接收的是空指针
	}
private:
	int _a;
};
int main()
{
	N2* p2 = nullptr;
	p2->print();
	return 0;
}

最后编译器就会报错。