【C++】类和对象1.0

本鼠浅浅介绍一些C++类和对象的知识,希望能得到读者老爷们的垂阅!

目录

1.面向过程和面向对象

2.类的引入

3.类的定义

4.类的访问限定符及封装

4.1.类的访问限定符

4.2.封装

5.C++中struct和class的区别

6.类域

7.类的实例化

8.类对象模型

8.1.类对象大小

8.2.类对象的存储方式

9.this指针 

9.1.this指针的引出

 9.2.this指针的特性

10.Question


1.面向过程和面向对象

作为了解:

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

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完 成。

没了,介绍完了。其实对于解决问题,C语言关注的是解决问题的动作,而C++关注的是解决问题的对象呗。

2.类的引入

俺们都知道C语言中有结构体的概念。那么C++兼容C语言的大部分语法,所以C++也可以使用结构体。但是C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数

举个栗子给老爷们瞅瞅:

#include<iostream>
#include<assert.h>
using namespace std;
typedef int SLTDateType;
struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
	//动态申请一个节点
	SListNode* BuyNode(SLTDateType x)
	{
		SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
		if (newnode == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		newnode->data = x;
		newnode->next = NULL;
		return newnode;
	}
	//单链表打印
	void Print(SListNode* plist)
	{
		SListNode* cur = plist;
		while (cur != NULL)
		{
			cout << cur->data << "->";
			cur = cur->next;
		}
		cout << "nullptr" << endl;
	}
	//单链表尾插
	void PushBack(SListNode** pplist, SLTDateType x)
	{
		assert(pplist);
		if (*pplist == NULL)//单链表为空
		{
			*pplist = BuyNode(x);
		}
		else//单链表不为空
		{
			SListNode* tail = *pplist;
			while (tail->next != NULL)//找尾
			{
				tail = tail->next;
			}
			SListNode* newnode = BuyNode(x);
			tail->next = newnode;
		}
	}
	//单链表头插
	void PushFront(SListNode** pplist, SLTDateType x)
	{
		assert(pplist);
		SListNode* newnode = BuyNode(x);
		newnode->next = *pplist;
		*pplist = newnode;
	}
	//销毁单链表
	void Destroy(SListNode** pphead)
	{
		assert(pphead);
		SListNode* cur = *pphead;
		while (cur)
		{
			SListNode* next = cur->next;
			free(cur);
			cur = next;
		}
		*pphead = NULL;
	}
};
int main()
{
	SListNode* sl = nullptr;
	sl->PushBack(&sl, 0);
	sl->PushFront(&sl, -1);
	sl->Print(sl);
	sl->Destroy(&sl);
	return 0;
}

 是可以运行的,且结果符合预期!!

上面结构体的定义,在C++中更喜欢用class来代替struct。这样C++中结构体就升级成了

3.类的定义


类的定义格式:class为定义类的关键字,ClassName为类的名字(可随意变换),{}中为类的主体,注意类定义结束时后面分号不能省略。

这样子:

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

类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数。 


类的两种定义方式:

1.方式一:声明和定义全部放在类体中。

需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

像这样子:

class person
{
	//成员变量的声明
	int age;
	const char* name;
	const char* sex;
	//成员函数的定义
	void showinformation()
	{
		cout << name << '-' << sex << '-' << age << endl;
	}
};

 2.方式二:类声明放在.h文件中,成员函数定义放在.cpp文件中。其实就是成员函数声明和定义分离。

注意:成员函数名前需要加类名::

像这样子:

类的.h文件(假如头文件为Person.h):

class person
{
	//成员变量的声明
	int age;
	const char* name;
	const char* sex;
	//成员函数的声明
	void showinformation();
};

类的.cpp文件:

#include"Person.h"

	//成员函数的定义
	void person::showinformation()
	{
		cout << name << '-' << sex << '-' << age << endl;
	}

一般情况下,更期望采用第二种方式。

4.类的访问限定符及封装

4.1.类的访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善(咱们看类可以有成员变量和成员函数,分别对应属性与方法)。通过访问权限选择性的将其接口提供给外部的用户使用。

类的访问限定符有三种:public(公有)、protected(保护)、private(私有)

类的访问限定符体现了访问权限

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

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


举一些栗子来证实访问限定符的作用:

#include<iostream>
using namespace std;
class person
{
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation()
	{
		cout << _name << '-'<<_sex <<'-'<< _age << endl;
	}
};
int main()
{
	person HD;
	HD._age = 20;//error C2248: “person::_age”: 无法访问 private 成员(在“person”类中声明)
	return 0;
}

主函数第二条语句编译不通过是因为: class的默认访问权限为private,private修饰的成员在类外不能直接被访问。

如果我们改用public修饰该类的所有成员:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation()
	{
		cout << _name << '-'<<_sex <<'-'<< _age << endl;
	}
};
int main()
{
	person HD;
	HD._age = 20;
	HD._name = "HD";
	HD._sex = "male";
	HD.showinformation();
	return 0;
}

 编译通过,运行成功!

又如果:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
private:
	void showinformation()
	{
		cout << _name << '-'<<_sex <<'-'<< _age << endl;
	}
};
int main()
{
	person HD;
	HD._age = 20;
	HD._name = "HD";
	HD._sex = "male";
	HD.showinformation();//error C2248: “person::showinformation”: 无法访问 private 成员(在“person”类中声明)
	return 0;
}

 主函数第五条语句编译报错。因为:该类的成员函数被private修饰。第二条到第四条语句编译通过是因为该类的所有成员变量被public修饰。(访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。)

4.2.封装

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

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

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

5.C++中struct和class的区别

C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类,和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类 默认访问权限是private。在继承和模板参数列表位置,struct和class也有区别,本鼠以后再介绍,因为本鼠也不清楚捏!

6.类域

类定义了一个新的作用域:类域,类的所有成员都在类的作用域中。与命名空间域一样,类域也只会影响访问,所以在类体外定义成员时,需要使用 :: 作用域限定符指明成员属于哪个类域。

举例请看类的定义方式二。

其实类的定义方式二中的成员函数的声明和定义分离写到同一个.cpp文件下面也行,像这样子:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation();
};
void person::showinformation()
{
	cout << _name << '-' << _sex << '-' << _age << endl;
}
int main()
{
	person HD;
	HD._age = 20;
	HD._name = "HD";
	HD._sex = "male";
	HD.showinformation();
	return 0;
}

 编译也通过,运行成功。

不过既然都写到同一个文件下了,成员函数的声明和定义分开写好像有种脱裤子放屁的无力感,没必要啊。。。 直接写成类的定义方式一不就好了。。。

当然同名变量可以在不同的域内定义,不同类域当然可以定义同名变量了。

7.类的实例化

类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。所以要让类有使用价值就要将类实例化。

用类类型创建对象的过程,称为类的实例化。说白了类的实例化就是创建类型为该类的对象呗。像这样子:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation();
};
void person::showinformation()
{
	cout << _name << '-' << _sex << '-' << _age << endl;
}
int main()
{
	person HD;//HD是person类实例化的对象
	return 0;
}

 定义出一个类并没有分配实际的内存空间来存储它,如上代码定义出person类,但是没有给person类开空间的。用person类类型创建对象HD,就是person类实例化,对象HD是开了空间来存储的。

一个类可以实例化出多个对象,很简单易懂吧,就像这样:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation();
};
void person::showinformation()
{
	cout << _name << '-' << _sex << '-' << _age << endl;
}
int main()
{
	person HD;//HD是person类实例化的对象
	person LCD;//LCD也是person类实例化的对象
	return 0;
}

HD和LCD都是person类实例化对象 。

实例化出的对象占用实际的物理空间,存储类成员变量,我们看:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation();
};
void person::showinformation()
{
	cout << _name << '-' << _sex << '-' << _age << endl;
}
int main()
{
	person._age = 10;//error C2059: 语法错误:“.”
	return 0;
}

编译报错是因为:person类是没有空间的,只有person类实例化出的对象才有具体的年龄。person类中的成员变量(如_age)只是声明,没有开空间来存储成员变量,只有person类实例化对象才有空间存储类成员变量(如_age)。

最后提一嘴:类对象实例化的时候代码可以带上class或者struct,也可以不带,一般不会带上。比如这样子写也能编译通过,运行成功:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation()
	{
		cout << _name << '-' << _sex << '-' << _age << endl;
	}
};
int main()
{
	class person HD;
	HD._age = 20;
	HD._name = "HD";
	HD._sex = "male";
	HD.showinformation();
	return 0;
}

 

8.类对象模型

8.1.类对象大小

我们也许会有疑问:C语言的结构体成员不可能有函数,计算结构体变量的大小遵循结构体内存对齐呗。但是类定义时可以包含成员变量和成员函数,那么类实例化对象的大小(占多少字节)该如何计算捏?

答案就是一个类实例化对象的大小(或者说类的大小),实际就是该类中”成员变量”之和,当然要注意内存对齐。就是说不用管类成员函数的存在。为什么这样子,本鼠下边再解释。

#include<iostream>
using namespace std;
class rectangle
{
public:
	double _long;
	double _wide;
	double _high;
	void showinformation()
	{
		cout << _long << '-' << _wide << '-' << _high << endl;
	}
};
int main()
{
	rectangle n1;
	cout << sizeof(rectangle) << endl;
	cout << sizeof(n1);
	return 0;
}

 

 额,sizeof(类)和sizeof(类实例化对象)是同一个意思,都是计算类实例化对象多大。

附上结构体内存对齐规则:

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

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。如果空类的大小为0个字节,那么该类实例化对象如何证明存在过捏?

#include<iostream>
using namespace std;
class N
{
};
int main()
{
	N n1;
	cout << sizeof(N) << endl;
	cout << sizeof(n1);
	return 0;
}

 

 好了,为啥子一个类实例化对象的大小(或者说类的大小),实际就是该类中”成员变量”之和,当然要注意内存对齐。就是说不用管类成员函数的存在。请看类对象的存储方式!!

8.2.类对象的存储方式

拿这个代码来说:

#include<iostream>
using namespace std;
class person
{
public:
	int _age;
	const char* _name;
	const char* _sex;
	void showinformation()
	{
		cout << _name << '-' << _sex << '-' << _age << endl;
	}
};
int main()
{
	class person HD;
	person LCD;
	return 0;
}

person类实例化对象有两个:HD和LCD。这两个对象个中成员变量是不同的(需要存储不同的属性,所以说是不同的),但是调用同一份函数(类方法是一样的)。当一 个类创建多个对象时,如果每个对象中都会保存一份代码,相同代码保存多次,浪费空间。 

那么计算机的做法是:当一个类创建多个对象时,对象只保存成员变量,成员函数存放在公共代码段(区)。

就是说当类实例化对象时,只会给该对象开遵循结构体内存对齐的“成员变量”之和的内存空间,当该对象需要用到成员函数时去公共代码段调用。

9.this指针 

9.1.this指针的引出

看一个代码:

#include<iostream>
using namespace std;
class Date
{
	int _year; // 年
	int _month;// 月
	int _day;// 日
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};
int main()
{
	Date d1, d2;
	d1.Init(2024, 6, 4);
	d2.Init(2003, 12, 12);
	d1.Print();
	d2.Print();
	return 0;
}

 

运行没毛病,但是有一个问题: 

 Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分。那当d1调用 Init 函 数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

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

this指针是隐藏起来的,拿上面代码来说:当d1调用Init函数时,传递的实参不仅仅有明面上的2024、6和4,还有该对象的地址,即d1的地址,而且第一个传递d1的地址。

Init函数有一个隐藏的this指针用来接收d1的地址,所以Init函数知道该设置d1对象而不是d2对象。

 9.2.this指针的特性

用本代码举例:

#include<iostream>
using namespace std;
class Date
{
	int _year; // 年
	int _month;// 月
	int _day;// 日
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};
int main()
{
	Date d1, d2;
	d1.Init(2024, 6, 4);
	d2.Init(2003, 12, 12);
	d1.Print();
	d2.Print();
	return 0;
}
  • this指针的类型:类类型* const。即成员函数中,不能给this指针赋值。比如上面代码Init函数隐藏的this指针类型是Data* const。
  • 在实参或者形参的位置上不能将this指针显示写,但是函数内部可以使用。

请看:

#include<iostream>
using namespace std;
class Date
{
	int _year; // 年
	int _month;// 月
	int _day;// 日
public:
	void Init(Date const* this, int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print(Data const*this)
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
};
int main()
{
	Date d1, d2;
	d1.Init(&d1, 2024, 6, 4);
	d2.Init(&d2, 2003, 12, 12);
	d1.Print(&d1);
	d2.Print(&d2);
	return 0;
}

编译报错!因为将形参位置隐藏的this指针显示写出来了,而且实参位置传入d1地址或者d2地址给this指针。这些都是编译器完成的,我们不应该显示写出来。

再看:

#include<iostream>
using namespace std;
class Date
{
	int _year; // 年
	int _month;// 月
	int _day;// 日
public:
	void Init( int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	void Print()
	{
		cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
};
int main()
{
	Date d1, d2;
	d1.Init( 2024, 6, 4);
	d2.Init( 2003, 12, 12);
	d1.Print();
	d2.Print();
	return 0;
}

编译通过,运行成功!函数内部可以使用隐藏的this指针。详情请看Init函数或者Print函数内部。

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

呃呃呃:this指针存储在栈区(有些编译器用寄存器存储),因为它是一个形参。

10.Question

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

#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

也许你会跟本鼠一样,看到空指针就想选A。但是正确答案是C。因为p虽然是空指针,但是Print函数并不是通过解引用p找到的,因为Print函数存储在公共代码段中,而不是存储在p指向的空间里。

 看看结果如图:

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

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

哈哈哈,答案选B。因为PrintA函数会将地址p作为实参传递给PrintA函数隐藏的形参this指针。我们看PrintA函数内部需要用到this指针解引用的哦,this指针接收到的值是空指针,而且成员变量_a是存储在this指向的空间里面的(成员变量存储在类实例化对象里面),当然会崩溃了。

崩溃了!!

感谢阅读!!!!!!! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X_chengonly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值