c++类和对象(代码片段)

正义的伙伴啊 正义的伙伴啊     2023-01-16     485

关键词:

对面向对象(OOP)的初步认识

  • C语言是面向过程 的,关注是处理数据的过程,分析出求解问题的步骤,通过函数调用逐步解决问题。数据 和 处理数据的方法是分离的。
  • C++是 基于面向对象 的,关注的是 对象 ,将一件事情拆分成不同的对象,靠对象之间交互完成。而C++ 将数据 和 处理数据的方法封装在一起,包含数据完整的生命周期。 但是C++也不是纯面向对象的语言,C++由于向下兼容C语言使得其也有面向对象的特性。

类的引入

C语言中我们学过 一种自定义类型——结构体 (可以参考博文:自定义类型详解
C语言中结构体是一种数据类型,可以表示不同数据类型的一种集合,在C++中对struct的作用进行了延申,struct 里面不仅可以定义 数据 还可以定义函数!


struct Student

	void SetStudentInfo(const char* s, int a)
	
		strcpy(name, s);
		age = a;
	
	void print()
	
		cout << name << "    " << age << endl;
	
	char name[20];
	int age;
;
int main()

	Student s;
	s.SetStudentInfo("Peter", 20);
	s.print();
	return 0;

这里就要引出我们在C++中更喜欢用class来代替struct来表示这种新的数据类——

类的定义

由上面的引入 得出了 组成类的成员

  • 类中的数据——成员变量
  • 类中的函数——成员函数

下面是类的定义方式:


class classname


	类的体:由成员函数 和 成员变量 组成

;   注意这里的分号

class ——定义类的关键字 ,classnaeme——类的名字,里包含的是类的主体。

类的定义方式:

  • 1.(函数)声明和定义放在一起(这里的定义指的是函数,成员变量在类中只是声明!)
class Student

	void SetStudentInfo(const char* s, int a)
	
		strcpy(name, s);
		age = a;

	
	void print()
	
		cout << name << "    " << age << endl;
	
	char name[20];
	int age;
;

注意: 这里在类里面定义函数 编译器一般会把其当成内联函数处理,所以小一点的函数可以在内里面定义,但是大一点的函数定义和声明还是分离比较好

  • 2.(函数)声明和定义分开

可以声明在头文件 Student.h中


class Student

public:
	void SetStudentInfo(const char* s, int a);
	void print();
	char name[20];
	int age;
;

定义放在 Student.cpp中,使用:: 运算符

#include<Student.h>
void Student::SetStudentInfo(const char* s, int a)

	strcpy(name, s);
	age = a;


void Student::print()

	cout << name << "    " << age << endl;

类的访问限定符及封装

访问限定符


说明

  1. public修饰的成员可以被类外直接访问
  2. proteced和private修饰的成员在在类外不可以直接被访问
  3. 访问权限的作用域 是从该访问限定符出现的位置到下一个访问出现的位置之前
  4. class的默认访问权限 为private,struct为public(因为要兼容C,这也是class和struct在表示类时的唯一区别)

问题:class和struct 有什么区别?
在C语言中struct可以表示成结构体去使用。C++由于兼容了C语言的特性,所以struct既能表示结构体,又能表示类并且和class作用一样,唯一不同的是:class的默认访问权限 为private,struct为public

封装

首先我们要了解一下面向对象的三大特性:封装、继承、多态
什么是封装呢?
封装是将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
说大白话:就是想让你访问的就是公有,不想让你访问的就设成私有,你必须通过成员函数才能与数据交互

总结
封装实际上是一种对数据的管理,防止乱访问数据造成的修改

类的作用域

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

class Student

public:
	void SetStudentInfo(const char* s, int a);
	void print();
	char name[20];
	int age;
;
//这里要指明setStudentinfo是来自Student这个类域
void Student::SetStudentInfo(const char* s, int a)

	strcpy(name, s);
	age = a;


void Student::print()

	cout << name << "    " << age << endl;

类的实例化

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

  1. 类只是一个模型,和struct表示的结构体一样是一个类型集合,定义一个类并没有实际给其分配内存空间来储存
  2. 一个类可以实例化多个对象,对象是类似于定义的变量,占有内存

打个比方:一个类定义出来就类似于一个图纸,类实例化出的对象就类似于按照图纸造出的房子,有了图纸你就可以造出房子,但图纸并没有实际存在的房子


class Student

public:
	void SetStudentInfo(const char* s, int a);
	void print();


	char name[20];   //变量声明 不开辟空间
	int age;
;

void Student::SetStudentInfo(const char* s, int a)

	strcpy(name, s);
	age = a;


void Student::print()

	cout << name << "    " << age << endl;



int main()

	Student s;   // 实例化的对象
	s.SetStudentInfo("Peter", 20);
	s.print();
	return 0;


类的大小的计算

如何计算一个类的大小呢?

  1. 解决这个问题首先要知道如何处理类中成员函数所占的空间
    这里成员函数其实是不存储在类里的,而是存储在内存分区中的 代码区。代码区存的都是在编译后 代码转换成的指令。而类的实例化是在堆栈上开辟的空间,所以在计算内存中无需考虑成员函数的大小,只 需要考虑成员变量。
  2. 然后类的大小分配原则和结构体的一模一样——都是按照内存对齐
    具体 可以参考博客:结构体的内存对齐规则

class Student

public:
	void SetStudentInfo(const char* s, int a);
	void print();
	char name[20];
	int age;
;

void Student::SetStudentInfo(const char* s, int a)

	strcpy(name, s);
	age = a;


void Student::print()

	cout << name << "    " << age << endl;



int main()

	cout << sizeof(Student) << endl;

由内存对齐规则可以知道结果是24

特殊的类的大小

class A

public:
	void f2();
;


class B
;

int main()


	cout << sizeof(Student) << endl;
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;

这里两个类 A 和 B并没有成员变量,按照上面的计算 内存应该为0,但是这里 人为的规定其大小为1 ,这里给一个字节是为了占位,表示对象存在,但是不存储任何有效数据!

类成员的this指针

我们先定义一个Data类:


class Data

public:
	void print()
	
		cout << "year: " << _year << " month: " << _month << " day: " << _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()

	Data d1;
	Data d2;
	d1.SetDate(2021, 10, 12);
	d2.SetDate(2020, 10, 12);

对于上述类,有一个问题:
Data类中由SetData与Display两个成员函数,函数体中没有关于不同对象的区分,那当d1调用SetData函数时,该函数是如何区分应该设置d1对象还是设置d2对象呢?

C++中通过引入this指针来解决这个问题,C++编译器给每个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作都是自动、隐式的,不用用户主动去调用。

this指针的特性

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

所以这里d1.SetDate(2021, 10, 12); 这句函数调用也就相当于 SetDate(&d1,2021,10,12);

注意:

  1. this指针存储在哪里?
    this指针不是存储在对象里面!!this指针是形参,形参和函数中的局部变量都是存储在函数栈帧里面的,实际上是由ecx寄存器传入
  2. this指针不能为空

下面这段代码能让我们更深刻的了解 成员函数 和 this指针


class A

public:
	void printA()
	
		cout << _a << endl;
	

	void show()
	
		cout << "show()" << endl;
	
private:
	int _a;
;


int main()

	A* p = nullptr;
	p->printA();  //语句1
	p->show();    //语句2

问题:

  1. 这段代码能通过编译吗?能正常运行吗?
  2. 单独运行语句2 能正常运行吗?

首先这段代码是可以通过编译的,但是会在运行阶段挂掉,且中断在printA函数的_a调用上。

一部分同学会认为编译无法通过的原因是 :p是一个空指针,对空指针解引用调用函数不是瞎搞吗?所以这里无法通过编译。这是对成员函数的存储位置还不是很了解,前面讲过成员函数是不储存在对象里面的,而是储存在内存上的代码区上,这里调用成员函数不回去访问p指向的空间,也就不存在对空指针解引用。
实际上:这里进行的操作是把 p(NULL)的值传给this指针!!

后面的现象也就很好解释了,传给this指针 show函数并没有调用类里面的变量,而printA函数调用了变量_a,而我们知道实际上这里调用的是this->_a,所以这里才出现了对空指针的解引用。

类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写任何东西的情况下都会默认生成 ——6个默认成员函数


注意:

  1. 默认成员函数在类定义之时,就会生成,但是如果自己定义了就不会再生成
  2. 默认成员函数也不是必须自己写,当默认成员函数能完成功能就不用自己写,如果不能完成功能 例如下面会讲到的 stack类,构造、析构、拷贝构造、赋值重载都要自己写!

构造函数

class Data

public:
	void print()
	
		cout << "year: " << _year << " month: " << _month << " day: " << _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()

	Data d1;
	Data d2;
	d1.SetDate(2021, 10, 12);
	d2.SetDate(2020, 10, 12);

前面在实现日期类的时候,写过一个函数是void SetDate(int year, int month, int day) 这个函数的主要目的是对成员变量进行初始化,这里其实的功能和构造函数一模一样,都是对类成员变量进行初始化,但是缺点也是很明显定义与初始化是分离的,每次定义完要调用函数初始化。构造函数直接在定义的时候就可以初始化了。

  • 构造函数——名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。

  • 默认构造函数——不用传参就可以调用的构造函数

特性

注意构造函数是初始化对象,而不是定义构造函数(开辟空间)
其特征如下:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载——提供多种初始化对象的方式。

class Data

public:
	Data()  //无参的构造函数
	
		;
	

	Data(int year , int month , int day ) //带参数的构造函数
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;

int main()

	Data d1;       
	Data d2(2021,7,26);
	
	Data d3(); //注意无参构造函数初始化对象时,对象后面不用跟括号,否则就成函数声明了
	//这里就是一个无参返回值是类Data的函数名为d3的函数



  1. 如果没有显示的定义构造函数,则C++编译器会自动生成一个无参默认构造函数,一旦用户显示定义编译器就不会再生成
class Data  //这里没有定义构造函数,编译器就会自己生成一个

private:
	int _year;
	int _month;
	int _day;
;

那么编译器生成的构造函数能完成什么功能呢?

  • 对待内置类型例如:int ,double ,指针,long…默认构造函数是不会初始化的
  • 对待自定义类型(class、struct)会调用它的 默认 构造函数(不用传参数的构造函数)

class A

public:
	A(int a = 100)
	
		_a = a;
	
	int _a;
;


class Data

	void print()
	
		cout << ps._a <<endl<<_year<<endl<<_month<<endl<<_day<< endl;
	
private:
	int _year;
	int _month;
	int _day;
	A ps;
;

int main()

	Data d1;
	d1.print();

这里在类Data成员变量中定义了 三个int类型和一个类A类型的变量,最后只有自定义类型的变量被初始化了


  1. 无参构造函数和全缺省构造函数 都称为默认构造函数,并且默认构造函数只能有一个! 注意:无参构造函数全缺省构造函数编译器自己生成的构造函数 统称为 默认构造函数

误区: 只有编译器自己生成的构造函数才是默认构造函数


class Data

public:
	Data()
	
		_year = 0;
		_month = 1;
		_day = 1;
	

	Data(int year=0 , int month=1 , int day=1 ) //两个默认构造函数不能同时存在
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;

不能存在两个默认构造函数,如果在类的实例化时不传参,编译器不知道调用哪一个构造函数。

总结:
大多数情况下构造函数都要自己去写,因为初始化出来的变量才会符合要求。一般情况下建议写一个全缺省的构造函数,这样可以应对各种场景。

参数列表

上面我们介绍了构造函数,其中有一种特殊的构造函数:默认构造函数。默认构造函数对待内置类型是不处理的,对待自定义类型是调用自定义类型的 默认构造函数 (注意是 默认构造函!!!!)
但是这里遗留了一个问题,如何对自定义类型里面的变量赋值?

class B

public:
	B(int x=1,int y=2)
	
		_x = x;
		_y = y;

	

	const B& operator=(const B& d1)
	
		_x = d1._x;
		_y = d1._y;

		return *this;
	
private:
	int _x;
	int _y;

;

class A

public:
	A(int a,int b,int c)
	
		B b2(b,c);
		b1 = b2;
		// b1= B(b,c)  使用匿名对象:生命周期只有这一行!
		_a = a;
	
	
private:
	int _a;
	B b1;
;

int main()

	A a1(100,100,100);

如果想要把值赋给类A中的类B成员变量,只能先创建一个临时变量赋值,然后再用赋值运算符重载拷贝给成员变量。这样定义类中的 类对象会十分麻烦。

这里我们就要介绍一下初始化列表:
初始化列表: 以一个冒号开始,接着是以一个以逗号分隔的数据成员列表,每个“成员变量”后面跟一个放在括号中的初始值或表达式

	A(int a,int b,int c)         //未显示定义参数列表,但是参数列表依然存在                            
	
		B b2(b,c);
		b1 = b2;
		_a = a;
	
	
    A(int a,int b,int c)//使用初始化列表
		:_a(a)
		,b1(b,c)
	

之所以参数列表能解决上面的问题,实际上是自定义类型会在参数列表处初始化(而自定义的 初始化 和 变量赋值是绑定在一起的),如果我们能在初始化的时候赋值就可以调用非默认构造函数了,而不是通过创建临时变量赋值重载。

参数列表不管你是否显示的写出来一直是存在的,而且一切变量都会在参数列表处初始化(如果我们对未显示定义参数列表的构造函数按f11一步一步的调试,会发现实际上在进入构造函数之前会跳到类B的默认构造函数)

注意:

  • 每个成员只能在初始化列表上出现一次

  • 类中包含以下成员必须在初始化列表处初始化:

    1. 引用成员变量
    2. const成员变量
    3. 自定义类型成员变量(该类没有默认构造函数)

可以发现必须在初始化列表初始化的成员变量有一个共性:定义的时候就必须赋初值,赋初值是和定义是绑定在一起的。

易错点



class A

public:

	A(int x)
		:_a1(x)
		, _a2(_a1)
	
	
	void print()
	
		cout << _a1 << "    " << _a2 << endl;
	

private:
	int _a2;
	int _a1;
;

int main()

	A a(1);
	a.print();
	return 0;

结果:很多人会认为结果是 1 1,但实际上是:

成员变量在类中声明次序就是其在初始化列表中初始化顺序,与其在初始化列表中的先后次序无关。

析构函数

概念:
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时

c++类和对象(上)(代码片段)

(文章目录)1.封装第一点<fontcolor=red>1.将数据和方法放到定义一起</font><fontcolor=Blue>c++类的成员函数即方法,成员变量即数据</font>#include<iostream>usingnamespacestd;classstackpublic: voidpush(intx);//成员函数pri 查看详情

c++学习:2类和对象(代码片段)

目录一、面向对象1、类和对象2、对象内存3、this4、封装二、内存空间1、内存空间2、堆内存堆空间的初始化3、成员变量的初始化三、构造函数(constryctor)1、构造函数的调用四、析构函数(Destructor)1、内存清理... 查看详情

c++类和对象补充(代码片段)

类和对象(下)一.再看构造函数1.函数体内赋初值2.初始化列表几点注意3.explicit关键字二.static成员1.概念2.特性三.友元1.友元函数2.友元类四.内部类一.再看构造函数我们之前已经了解了构造函数的基本内容,那么这里... 查看详情

c++类和对象(代码片段)

文章目录再谈构造函数构造函数体赋值初始化列表explicit关键字static成员概念特性static的作用C++11的成员初始化友元友元函数友元类内部类的概念及特性再次理解封装再次理解面向对象再谈构造函数构造函数体赋值在创建对... 查看详情

c++初识类和对象(代码片段)

类和对象(上)一.初步认识面向过程和面向对象二.类的引入三.类的定义四.类的访问限定符及封装1.访问限定符2.封装五.类的作用域六.类的实例化七.类对象模型1.计算类对象的大小2.类对象的存储方式八.this指针1.this指针... 查看详情

c++类和对象1(代码片段)

文章目录前言一、类的引入二、类的定义三、类的访问限定符及封装1.访问限定符2.封装四、类的作用域五、类对象模型1.如何计算类对象的大小2.类对象的存储方式猜测(1)类实例化出的对象中包含所有成员(2)... 查看详情

植物大战类和对象——c++(代码片段)

...;扶摇可接。东隅已逝,桑榆非晚。”这里是目录一、类和对象入门概念1.类class2.类的访问限定符3.类的作用域4.类的实例化5.类对象的存储方式二、什么是面向对象?三、什么是类?四、类的定义方式五、类的作用域... 查看详情

c++类和对象(代码片段)

目录面向过程和面向对象初步认识类的定义类的访问限定符及封装访问限定符封装类的作用域类的实例化类的对象大小计算类成员函数this指针面向过程和面向对象初步认识C语言是面向过程的,关注的是过程,分析出求解... 查看详情

c++类和对象4(代码片段)

文章目录一、再谈构造函数1.构造函数体内赋值2.初始化列表(1)格式(2)用处3.静态成员二、类成员默认值(C++11)三、内部类感谢阅读,如有错误请批评指正一、再谈构造函数1.构造函数体内... 查看详情

c++类和对象(代码片段)

文章目录前言一、对象是什么?二、对象的定义和类的引入1.类的组成和实现1.类的组成2.类的实现3.类成员变量和函数的权限1.类中成员的权限2.类的作用域三、this指针和类的大小1.this指针2.this指针的特性3.类的大小前言c是面... 查看详情

c++类和对象(代码片段)

文章目录前言一、对象是什么?二、对象的定义和类的引入1.类的组成和实现1.类的组成2.类的实现3.类成员变量和函数的权限1.类中成员的权限2.类的作用域三、this指针和类的大小1.this指针2.this指针的特性3.类的大小前言c是面... 查看详情

c++类和对象(代码片段)

文章目录前言一、对象是什么?二、对象的定义和类的引入1.类的组成和实现1.类的组成2.类的实现3.类成员变量和函数的权限1.类中成员的权限2.类的作用域三、this指针和类的大小1.this指针2.this指针的特性3.类的大小前言c是面... 查看详情

c++再识类和对象(代码片段)

类和对象(中)类的6个默认成员函数构造函数1.概念2.特性函数名与类名相同。没有返回值。编译器会再对象实例化时自动调用构造函数。构造函数可以重载。隐式构造函数无参和全缺省的函数均为默认构造函数成员变量... 查看详情

c++类和对象之封装(代码片段)

封装封装封装的意义struct和class区别成员属性设置为私有C++面向对象的三大特性为:封装、继承、多态C++认为万事万物都皆为对象,对象上有其属性和行为例如:​人可以作为对象,属性有姓名、年龄... 查看详情

c++初阶类和对象(代码片段)

目录面向过程和面向对象初步认识类的引入 类的定义类的访问限定符: 封装类的作用域类的实例化类对象模型1.如何计算类对象的大小2.类对象的存储方式猜测this指针1.this指针的引出2.this指针的特性面向过程和面向对象初... 查看详情

c++类和对象2(代码片段)

文章目录一、this指针1.this指针的引入2.this指针的特性3.两个小问题(1)this指针存储在哪里?(2)this指针可以为空(nullptr)吗?二、类的6个默认成员函数三、构造函数1.概念2.特性(1)函... 查看详情

c++类和对象(代码片段)

文章目录类的引入类的定义类的访问限定符及封装访问限定符类的封装类的作用域类的实例化类对象模型如何计算类对象的大小类对象的存储方式猜测结构体内存对齐规则this指针this指针的引出this指针的特性类的引入C语言中ÿ... 查看详情

c++类和对象3(代码片段)

文章目录一、析构函数1.概念2.特性(1)函数名(2)无参数无返回值(3)一个类有且只有一个析构函数,编译器自动调用(4)编译器生成的默认的析构函数二、拷贝构造函数1.概念2.特性(1... 查看详情