Bootstrap

C++初阶---类和对象(入门)

1)面向过程与面向对象

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题


C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
注意: C++是基于面向对象语言,不像Java等是面向对象语言


2)类

①类(class)的引入和定义

引入

C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数,已经升级为类

例如:

struct Student
{
	void SetStudentInfo(const char* name, 
						const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}
	void PrintStudentInfo()
	{
		cout<<_name<<" "<<_gender<<" "<<_age<<endl;
	}
	char _name[20];
	char _gender[3];
	int _age;
};

主函数中调用即可

Student s;
s.SetStudentInfo("Jack", "male", 38);

但在C++中更喜欢用class关键字来代替struct


C++中struct和class的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是
private


定义
class className
{
	// 类体:由成员函数和成员变量组成
};//注意分号

类中的元素称为类的成员,类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数

类的两种定义方式

  1. 声明和定义都在类体中
class person
{
public:
	void test()
	{
		//code...
	}
private:
	char _a;
	char _b;
	int _c;
};
  1. 声明在类体中,定义放在类实现文件中
class person
{
public:
	void test();
private:
	char _a;
	char _b;
	int _c;
};
//分开
void person::test()
{
	//code...
}

注意

  1. 成员函数如果在类中定义,编译器可能会将其当成内联函数处理
    (小于10行)
    参考C++基础入门概览的inline函数部分
  2. 一般情况下,更期望采用第二种方式

②类的访问

1. (初识)类的访问限定符
  1. public
    修饰的成员在类外可以直接被访问
  2. private
    修饰的成员在类外不能直接被访问
  3. protected
    此处暂时认为protected和private是类似的

注意

  1. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  2. 在类的成员全部都是pulic的时候,我们可以使用struct来定义类
  3. 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

2. 类的作用域

如上面定义类的第二种方式所示,在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域

void person::test()
{
	//code...
}

3. 面向对象的三大特性之一:封装

面向对象的三大特性:封装,继承,多态
注意:面向对象的特性不止这三个

封装的定义:

将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
(类的定义就体现了封装)

封装的好处:

我们使用类数据和方法都封装一下。不想让别人看到的,我们使用protected/private把成员封装起来,开放一些共有的成员函数对成员合理的访问,封装本质是一种管理
不封装会要求使用的人必须非常规范


③类的实例化和对象模型

实例化

用类创建对象的过程,称为类的实例化在这里插入图片描述
注意

  1. 类只是一个图纸一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
1. 计算类对象的大小

例1

class A1
{
public:
	void PrintA()
	{
		cout<<_a<<endl;
	}
private:
	int _s
	char _a;
};

例2:

// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};

例3:

// 类中什么都没有---空类
class A3
{};

sizeof(A1) sizeof(A2) sizeof(A3) 分别为8 , 1 , 1
(注意:sizeof(A)算的是这个类定义出来的对象的大小,不是类这个类型的大小)
请看类对象的存储方式

2. 类对象的存储方式
  1. 对象中包含类的各个成员
    在这里插入图片描述
    缺陷每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间,所以我们采用第二种存储方式

  1. 只保存成员变量,成员函数存放在公共的代码段
    在这里插入图片描述

总结
一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类(此1字节不存储有效数据)

3. 结构体内存对齐规则

与C语言中的结构体内存对齐规则完全一致
参考C语言----结构体,枚举,共用体

4. 定义匿名对象

假设Widget是一个类,我们可以定义匿名对象

Widget();//定义一个匿名对象

由于匿名对象的生命周期只在本行,不同于一般对象是在所在函数
如果我们想调用函数可以这样:

Widget().Print();

3)this指针

1.概念

例子

class Date
{
public :
	void Display ()
	{
		cout <<_year<< "-" <<_month << "-"<< _day <<endl;
	}
	void SetDate(int year , int month , int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};
int main()
{
	Date d1, d2;
	d1.SetDate(2018,5,1);
	d2.SetDate(2018,7,1);
	d1.Display();
	d2.Display();
	return 0;
}

打印 2018-5-12018-7-1


创建了两个实例化对象d1,d2,调用函数时是如何知道是d1还是d2呢?


this指针C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
特性

  1. this指针的类型:类类型* const
  2. 只能在“成员函数”的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

所以

void Display()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

其实是:

void Display(Data* this)
{
	cout << this->_year << "-" << this->_month << "-" << this->_day << >endl;
}

2.this指针的一些问题

1.this指针存在哪

this指针是形参,形参和函数中的局部变量都是存在函数栈帧里,所以this指针可以认为存在栈里

2.this指针可否为空指针

例子:

class A
{
public:
	void PrintA()
	{
		cout<<_a<<endl;
	}
	void Show()
	{
		cout<<"Show()"<<endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	p->Show();
}

调用PrintA()函数直接崩溃,而调用Show()函数正常运行


分析:

  1. 成员函数地址不在对象中存储,在公共代码段,这里调用成员函数Show(),不会访问p指向的空间,也就不存在空指针解引用了,这里智慧把p传递给隐含this指针。但是Show()函数中也没有解引用this指针
  2. 同理,PrintA()函数解引用了空指针,所以崩溃

;