C++:类和对象——“类的详细介绍”,“this指针”

目录

1.面向过程和面向对象初步认识

2.类的引入

注意1:

注意2:

 3.类的定义

4.访问限定符

5. 封装

6.类的作用域

(1)概念

(2)细节1:不同的类域是可以有同名函数的,并且跟函数重载没关系

(3)细节2:在类里面定义的函数默认是inline

补充知识点1:声明和定义的区分:开辟了空间就是定义!

6.类的实例化

7.类对象的存储方式

结构体内存对齐规则

8.this指针

(1)this是C++新增的关键字

(2)this指针的特性

(3)关于this指针的一个面试题

面试题1

补充知识点2:编译错误和运行崩溃区别

面试题2

面试题3


1.面向过程和面向对象初步认识

C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++ 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成。

 拿外卖来举例子,C语言面向过程看的是:点外卖,送外卖,拿外卖。C++面向对象看的是:商家,骑手,用户。

2.类的引入

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

 C++兼容C struct的用法
 C++同时对struct进行了升级,把struct 升级成了类
 1、结构体名称可以做类型
 2、里面可以定义函数

struct Student
{
	void Init(const char* name, const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

	void Print()
	{
		cout << _name << " " << _gender << " " << _age << endl;
	}

	// 这里并不是必须加_
	// 习惯加这个,用来标识成员变量
	char _name[20];
	char _gender[3];
	int _age;
};
int main()
{
	struct Student s1;
	Student s2;
	s1.Init("张三", "男", 18);
	s1.Print();

	return 0;
}

注意1:

struct Student就是一个类,类是一个整体,无论成员变量放在前中后,成员函数都可以都可以使用

注意2:

成员变量加_的目的,比如char _name[20]:为了和形式参数区分,如果不加_区分,当成员函数执行时,就近原则,就会把name,gender这些认为是形参而不是成员变量,运行就会错误

正确:
void Init(const char* name, const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

错误:
void Init(const char* name, const char* gender, int age)
	{
		strcpy(name, name);
		strcpy(gender, gender);
		age = age;
	}
(2)上面结构体的定义, C++ 中更喜欢用 class 来代替;变量在C++中要改叫成“对象”,那么下面就是类和对象了:
class Student        //类
{
	void Init(const char* name, const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

	void Print()
	{
		cout << _name << " " << _gender << " " << _age << endl;
	}

	// 这里并不是必须加_
	// 习惯加这个,用来标识成员变量
	char _name[20];
	char _gender[3];
	int _age;
};
int main()
{
	Student s2;   //对象     
	s1.Init("张三", "男", 18);
	s1.Print();

	return 0;
}
(面试题)问题: C++ struct class 的区别是什么?
解答: C++ 需要兼容 C 语言,所以 C++ struct 可以当成结构体去使用。另外 C++ struct 还可以用来定义类。
class 是定义类是一样的,区别是 struct 的成员默认访问方式是 public class 是的成员默认访问方式是
private

 3.类的定义

class className
{
 类体:由成员函数和成员变量组成
 
};  一定要注意后面的分号

4.访问限定符

(1)C++ 实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其 接口提供给外部的用户使用
访问限定符有三个:
C++:类和对象——“类的详细介绍”,“this指针”_第1张图片

 private私有 是防止你在类外面用对象直接访问:拿栈举例子

class Stack
{
public:
	void Init();
	void Push(int x);
	int Top();
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack st1;
	st1._top;	这样就是从类外面访问,错误!
}
从类里面访问才可以:
当类定义在stack.h文件中:
class Stack
{
public:
	void Init();
	void Push(int x);
	int Top();
private:
	int* _a;
	int _top;
	int _capacity;
};

.cpp文件中访问
#include"stack.h"
void Stack::Init()
{
	_a = nullptr;
	_top = 0;
	_capacity = 0;
}

【访问限定符说明】
  1. public修饰的成员在类外可以直接被访问
2. protected和 private 修饰的成员在类外不能直接被访问 ( 此处 protected private 是类似的,只要在继承部分才有区别)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4.如果后面没有访问限定符,作用域就到}类结束。
5. class 的默认访问权限为private struct public( 因为 struct 要兼容C)  
// struct 不加访问限定符,默认是public
// class 不加访问限定符,默认是private
class Student
{
public:
	// 类体:由成员函数和成员变量组成
	void Init(const char* name, const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

	void Print()
	{
		cout << _name << " " << _gender << " " << _age << endl;
	}

private:
	char _name[20];
	char _gender[3];
protected:
	int _age;
};

int main()
{
	Student s2;
	s2.Init("张三", "男", 18);
	s2.Print();

	//cout << s2._name << endl;     类外面无法访问private修饰的类的成员变量
	//cout << s2._age << endl;      类外面无法访问protected修饰的类的成员变量

	return 0;
}

5. 封装

【面试题】 面向对象的三大特性: 封装、继承、多态
在类和对象阶段,我们只研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。
封装本质上是一种管理 :我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们 首先建了一座房子把兵马俑给封装 起来。但是我们目的全封装起来,不让别人看。所以我们 开放了售票通 ,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。 不想给别人看到的,我们使用protected/private 把成员 封装 起来。 开放 一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。
把C++封装起来的意义:封装起来会更严格管理设计
因为C语言数据和方法分离,太过自由,定义和使用也绕在了一起,初始化等等容易被乱改,需要使用者的高素养
C++强制严格了起来,不自由,使用时只需调用C++成员函数的接口,私有的成员变量和C++调用的地方没有关联,互不影响,这叫低耦合

封装:更严格管理设计

1、数据和方法封装到一起,类里面

2、想给你自由访问的设计成共有,不想给你直接访问的设计成私有

 一般情况设计类,成员数据都是私有或者保护,想给使用者访问的函数是共有,不想给使用者访问时私有或保护

举例:

class Stack
{
private:
	void Checkcapaicty()    //增容接口不想让使用者访问也可以设为私有
	{}
public:
	void Init()
	{}

	void Push(int x)
	{}

	int Top()
	{}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	st.Push(2);
	st.Push(3);
	st.Push(4);

	cout << st.Top() << endl;
	//cout << st._a[st._top] << endl;

	return 0;
}

6.类的作用域

(1)概念

类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout<<_name<<" "_gender<<" "<<_age<

(2)细节1:不同的类域是可以有同名函数的,并且跟函数重载没关系

 两个类域
class Stack
{
public:
	void Push(int x)
	{}
};

class Queue
{
public:
	void Push(int x)
	{}
};

(3)细节2:在类里面定义的函数默认是inline

class Stack
{
public:
	// 在类里面定义
	// 在类里面定义的函数默认是inline
	void Init()
	{
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}

	// 在类里面声明, 在.cpp里面定义
	void Push(int x);
	void Pop();
	// 总结一下:实际中,一般情况下,短小函数可以直接在类里面定义,长一点函数声明和定义分离
private:
	// 声明
	int* _a;
	int _top;
	int _capacity;
};

总结:实际中,一般情况下,短小函数可以直接在类里面定义,长一点函数声明和定义分离

————————————————————————————手动分割符————————

补充知识点1:声明和定义的区分:开辟了空间就是定义!

类中的成员变量也是声明

private:
	// 声明
	int* _a;
	int _top;
	int _capacity;

类中的成员变量的定义在哪?:在创建对象的时候,开辟了成员变量的空间,所以属于定义(此时st1内部的值未初始化都是随机值)

int main()
{
	Stack st1;
}

————————————————————————————手动分割符————————

6.类的实例化

用类类型创建对象的过程,称为类的实例化
1. 类只是 一个 模型 一样的东西,限定了类有哪些成员,定义出一个类 并没有分配实际的内存空间 来存储它
2. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量
3. 做个比方。 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

7.类对象的存储方式

只保存成员变量,成员函数存放在公共的代码段(静态变量区)C++:类和对象——“类的详细介绍”,“this指针”_第2张图片

①类(或类的对象)的大小不包括成员函数,因为成员函数存放在公共的代码段

类大小不包括静态成员变量,静态成员变量在静态区

③类大小不包括内部类,内部类详情请见博客:(129条消息) 友元的详解_beyond.myself的博客-CSDN博客

所以正常按内存对齐计算就行

结构体内存对齐规则

1. 第一个成员在与结构体偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS 中默认的对齐数为 8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

举个常规例子:

C++:类和对象——“类的详细介绍”,“this指针”_第3张图片

 过程:_a 自身大小4字节,默认对齐数是8, 4 和 8取较小值4作为对齐数,则需要从对齐到4的整数倍的空间开始,占内存的0~3个字节,_ch 自身大小1字节,1 和 8取较小值1作为对齐数,则需要从对齐到1的整数倍的空间开始,占内存的第4个字节,一共5个字节,内存对齐:最大对齐数是三个对齐数1 4中的4,则结构体的总大小是4的倍数,则让 5 再浪费三字节空间到了偏移量 8 的位置,总共8字节空间

C++:类和对象——“类的详细介绍”,“this指针”_第4张图片

例子2:没有成员变量的类对象多大?空类多大?——都是1字节

因为:没有成员变量的类对象,编译会给他们分配1byte占位,表示对象存在过 C++:类和对象——“类的详细介绍”,“this指针”_第5张图片

8.this指针

(1)this是C++新增的关键字

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	/*void Print(Date* const this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}*/
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	/*void Init(Date* const this, int year, int month, int day)
	{
	this->_year = year;
	this->_month = month;
	this->_day = day;
	}*/

private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1;
	Date d2;

	d1.Init(2022, 5, 11);
	d2.Init(2022, 5, 12);
	//d1.Init(&d1, 2022, 5, 15);
	//d2.Init(&d2, 2022, 5, 20);

	d1.Print();
	d2.Print();
	//d1.Print(&d1);
	//d2.Print(&d2);

	return 0;
}
Date 类中有 Print Init 两个成员函数,函数体中没有关于不同对象的区分,那当 d1调用Print函数时,该函数是如何知道应该设置 d1 对象,而不是设置 d2对象呢?
C++ 中通过引入 this 指针解决该问题,即: C++ 编译器给每个 非静态的成员函数 增加了一个隐藏的指针参 数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有成员变量的操作,都是通过该 指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

调用时this指针是隐含的,d1调用Print函数时,传参传过去就是d1的地址,Print函数接收时利用隐含的this指针接收了d1的地址,内部的成员变量前面默认都是this指向的,比如_year相当于      this->_year,

当然①类里面可以显示的把访问写出来,即:可以自己写上 this-> 。(不写编译器也会自己加上,所以我们一般都不加this->)

②也可以在类中打印this,即:cout<< this <

③但是不可以在实参和形参位置显示的写出this,即: void Print() 不允许写成 void Print(Date* this) ,不允许在形参加上this

④//this =nullptr; 是错误的: this指针本身不能修改,因为他是const修饰的
// this指向对象可以被修改 this->_year=year;

 

    d1.Print();    //d1.Print(&d1);
	d2.Print();    //d2.Print(&d2);
    void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
Print加上this指针后就是如下:
    //void Print(Date* this)
	//{
	//	cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	//}

(2)this指针的特性

1. this 指针的类型:类类型 * const
比如:
Date d1;
d1.Init(2022, 5, 11);    //d1.Init(&d1, 2022, 5, 15);
类里面成员函数:
void Init(int year, int month, int day)
//void Init(Date* const this, int year, int month, int day)

2. 只能在成员函数的内部使用

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

(3)关于this指针的一个面试题

面试题1

【1】下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行

class A
{
public:
	void Show()
	{
		cout << this << endl;    //打印空指针也没错
		cout << "Show()" << endl;
	}
//private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Show();
    //如果是 p->_a; 就有错
}

————————————————————————————手动分割符————————

补充知识点2:编译错误和运行崩溃区别

在编译成中间语言的时候就没通过,也就是语法有错误,简单的说,

就是"你说的话,人家完全听不懂,没法帮你编译"所以是编译错误,

这个是原则性的错误.这个图就是语法错误!!C++:类和对象——“类的详细介绍”,“this指针”_第6张图片

而编译通过了,但是运行时错误,说明编程人员对代码的思想有错误,

简单的说就是"你说的话,人家听得懂,但是不明白你什么意识",语无伦次的

让对方不知道你想做什么,就运行时错误了.

————————————————————————————手动分割符————————

选C、正常运行。首先一定不能选A,因为野指针问题都是运行时崩溃,编译器并不能自己检查出来

尽管p是空指针,但空指针是实参,把空指针实参传给形参是没错的,并且p-> Show()  ,Show()成员函数放在公共的代码区,编译时去公共的代码区找到函数Show(),转换成 “call(函数的地址)”,就是普通的函数调用,cal1  A: :Show ( 0BA14D8h),并没有解引用空指针

 p->_a; 类里面只存成员变量,访问了空指针的成员变量,解引用空指针会报错,所以错误,但是不会崩溃,编译器优化会忽略这个代码,因为什么事都没做。如果是访问修改这个代码就会报错: p->_a=0;

面试题2

【2】下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行

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

int main()
{
	A* p = nullptr;
	p->PrintA();

}

 选B、运行崩溃   ,访问了空指针的成员变量,会解引用空指针,运行崩溃,但语法还是对的,所以不是A是B。        

面试题3

1. this指针存在哪里?栈 堆 静态区 常量区 类中?
在函数的栈帧中,属于栈,有些编译器会使用寄存器优化,存放到寄存器,因为如果频繁使用this为了提高效率,会把它放掉寄存器中

 

你可能感兴趣的:(C++前期,c++,开发语言)