您现在的位置是:首页 >学无止境 >详解C++类&对象(上篇)——超详细网站首页学无止境

详解C++类&对象(上篇)——超详细

花果山~~程序猿 2024-05-30 13:36:12
简介详解C++类&对象(上篇)——超详细

目录

一,面向对象&面向过程的认识(简单了解即可,逐步认识)

二, 类

2.1 类的引入 

2.2 类的定义

1. struct 

2. class 

类的两种定义方式:

2.3 封装&类的访问限定符 

1. 封装概念

2. 类的访问限定符 

2. 4 类的作用域

2. 5 类对象模型

1. 计算类大小

2. 类对象的存储方式

2.6  结构体内存对齐规则

2. 7  this指针

1. this指针特性

2. this指针面试题

结语


 

一,面向对象&面向过程的认识(简单了解即可,逐步认识)

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

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。(C++也可以是面向过程&面向对象混合编写)

 二, 类

2.1 类的引入 

    C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。
比如:
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;

struct man
{
	char _name[10];
	int _age;

	void manitin(const char* name, int age) // 文件是cPP时不会报错,C文件会报错
	{
	}
};

int main()
{
  man z;  // 在C++自定义一个类,形成一个新的类型,同时也是类名。
  return 0;
}

2.2 类的定义

1. struct 

// C中结构体
struct MyStruct 
{
	int a;
	int b;
	// 函数无法写入
};

// C++为了兼容C,C模式写法在C++编译器下不会报错,同时
// C++将C升级为了类,让函数可以存在于类中。
struct man
{
	int a;
	int b;
	void func(int a, int b)
	{
		;
	}
};

2. class 

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号
(class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意:类定义结束时后面分号不能省略。)
类体中内容称为类的成员:
  • 类中的变量称为类的属性成员变量;
  • 类中的函数称为类的方法或者成员函数

虽然有C的struct,但C++跟普遍使用class,这个后边会讲解。 

类的两种定义方式:

1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器 可能(代码比较少) 会将其当成 联函数处理。  

2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::函数名( namespace命名空间,也是如此,空间名::成员名)——————(当我们完成大项目时,更推荐这种,声明与函数定义文件分离,结构更清晰)

2.3 封装&类的访问限定符 

1. 封装概念

面向对象的三大特性:封装、继承、多态。 

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

就像一部电脑,外面被金属壳封装,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用
    而电脑封装需要壳子,那么代码封装就有了类的的访问限定符。

2. 类的访问限定符 

 

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

基本用法如下:

class man  // 默认private
{
private:  // 这一句可以不写——不允许类外部直接访问
	int a;
	int b;

public:   // 从这里开始允许类外部直接访问
	void func(int a, int b)
	{
		cout << a + b << endl;
	}
};

int main()
{
	man k;
	k.func(1, 2);
	//k.a; // 报错:不可访问
	return 0;
}

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

2. 4 类的作用域

    类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

class man  // 默认private
{
private:  // 这一句可以不写——不允许类外部直接访问
	int a;
	int b;

public:   // 从这里开始允许类外部直接访问
	void func(int a, int b);
};

void man::func(int a, int b)
{
	cout << a + b << endl;
}

2. 5 类对象模型

1. 计算类大小

class man
{
	int age;
public:
	void func()
	{
		;
	}
};
class A
{
public:
	void A1()
	{
		;
	}
};
int main()
{
	cout << sizeof(man) << endl;  // 4字节,可见类成员函数并不存在于类中
	cout << sizeof(A) << endl;    // 没有成员变量的类,给1个字节,表示存在这个类对象。
	return 0;
}

2. 类对象的存储方式

 我们来测试下面代码,看看我们的VS2019的编译器是否是这样的方式?

class man
{
	int age;
public:
	void func()
	{
		;
	}
};

int main()
{
	man a;
	man b;
	man c;
	a.func();
	b.func();
	c.func();
	return 0;

 然后我们查看反汇编。

 

我们会发现三个实例,调用的函数是同一个函数

这里我们直接进行解释吧,在编译时期,根据函数名就找到类成员函数所在的公共代码区地址,不用等到链接时期查找填充地址。

这里有一个代码验证上面结论,如下:

class man
{
	int age;
public:
	void func()
	{
		;
	}
};

int main()
{
	man* k = nullptr;
	k->func();
	return 0;
}

 

  

 可见,在两种调试信息格式下结果,可见:

  • 1. 不同的类实例,他们所调用的类内部函数都是同一个且存储在公共代码区 。
  • 2. 类内部的函数可能是内联函数, 在编译时期,调用函数是直接展开。

2.6  结构体内存对齐规则

  • 1. 第一个成员在与结构体偏移量为0的地址处。
  • 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 (注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8)
  • 3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  • 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  • 如果还是不怎么了解,请查看这篇 详解C语言中结构体,枚举,联合体易错点补充_花果山~~程序猿的博客-CSDN博客

 2. 7  this指针

        我们看下面代码:

class man
{
	int _age;
	int _tall;

public:
	void manInit(int age, int tall)
	{
		_age = age;
		_tall = tall;
	}
};

int main()
{
	man li;
	man ikun;

	li.manInit(20, 165);
	ikun.manInit(25, 180);
	return 0;
}

我们知道同一个类函数,其的代码放在公共代码区, 我们对li进行初始化,然后对ikun进行初始化,都是相同的一个函数都是_age = age;
        _tall = tall;不禁会想,ikun的初始化会不会覆盖li的数据呢??

的确这么低级的错误,科学家们想到了this值指针,C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

 补全后就长这样,但会报错:

class man
{
	int _age;
	int _tall;

public:
	void manInit(man* const this ,int age, int tall) // 编译器会自动添加一个不可修改指向的this指针,不允许人为添加
		                                             // 这样就解决了找不到对象的问题。
	{
		cout << this << endl;  // 无法修改但可以读
		this->_age = age;      // 自动隐式添加
		this->_tall = tall;    // 自动隐式添加,这里只是人为显示
	}
};

int main()
{
	man li;
	man ikun;

	li.manInit(&li ,20, 165);
	ikun.manInit(&ikun, 25, 180);
	return 0;
}

1. this指针特性

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

2. this指针面试题

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
 void Print()
 {
 cout << "Print()" << endl;
 }
private:
 int _a;
};
int main()
{
 A* p = nullptr;
 p->Print();
 return 0;
}

 结果是C.  正常运行。 解析:这个同前面题类似,print()是类成员函数,且代码短小,被当做内联函数处理,在编译时期就将调用处替换,并且未对p解引用。

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

 结果是B.运行崩溃。 解析:print()是类成员函数,被当做内联函数处理,在编译时期就将调用处替换,同时传nullptr给this指针,后面对this(nullptr)进行解引用因此崩溃。

结语

本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。