C++中的结构体与类

  • Post author:
  • Post category:其他




C++中继承了C语言中的结构体,但同时也在C语言的基础上新增了一个类(class)的概念,类可以说是C语言中结构体的升级版,这里主要细讲C++中的类


1、C++结构体



2、C++类




1、c语言结构体:



在C语言中定义一个结构体变量时需要加上struct关键字的,但在C++中的结构体在定义变量时是可以不写struct关键字的,而且C++的结构体也有权限,权限默认为:公有(public),但结构体内不存在任何函数;测试代码:
#include <iostream>

struct AA
{
        int num;
};

int main()
{
        AA a;//可以不写struct关键字定义变量
        a.num = 5;
        std::cout << a.num << std::endl;
}

其他的C语言部分就不讲了,这里只讨论C++在C语言基础上做了什么改进


(注:C++中当结构体中没有成员时,结构体占一个字节)




2、C++中的类(class):


2.1、类与对象的关系



2.2、C++成员权限



2.3、类的定义



2.4、类的大小



2.5、类中隐含的指针



2.6、类中自带的函数



2.7、类继承



2.8、类成员方法默认参数



2.9、抽象类



类是C++中面向对象编程思想中比较重要的组成部分,与结构体一样类只是一个模板只有在创建对象时才会申请内存空间,类其实是把具有共同特性的数据或方法(面向对象编程中,一般把函数称为方法)都放在一起,用于创建对象时使用,下面分步骤来阐述C++的类:




(1)类与对象的关系

类是对象的创建模板

对象是类的实例化




(2)C++类成员权限

C++中为类成员设置访问权限,权限分为三种:


private(私有成员)

:只能类内进行访问


protected(保护成员)

:只能在类内或派生类中去访问


public(公有成员)

:可以类内或者类外进行访问
在这里插入图片描述




(3)类的定义

定义时需要先写类的关键字class,后面跟着类名;花括号内是存放类成员的范围,花括号后需要与结构一样加个分号

示例代码:

class 类名
{
成员权限:
	类成员(变量,方法)
};




(4)类的大小

在C++中类的大小与成员数据有关,与成员方法无关

测试代码:

#include <iostream>

class AA
{
public:
       void print(){
			int num = 5;
			std::cout << num << std::endl;
		}
       
        int a;  
};

int main()
{
        AA a;
      	std::cout << sizeof(a) << std::endl;
    
        return 0;
}

运行结果:

在这里插入图片描述

由此可以证明C++中,类的大小只与成员数据有关,与成员方法无关;


(注:当类中没有成员数据时,或只有成员方法时;类占一个字节)




(5)类中隐含的this指针

其实我们创建的每一个C++类对象中都隐含有一个this指针,它存储着当前类对象的地址,因此我们可以通过this指针访问当前对象里的值

示例代码:

#include <iostream>

class AA
{
public:
        void print() {
                //当我们创建的类对象调用print函数时,打印当前类对象中a的值
                std::cout << this->a << std::endl;
        }
       
        int a;  
};

int main()
{
        AA a;
        a.a = 10; 
        a.print();
    
        AA b;
        b.a = 20; 
        b.print();
    
        return 0;
}


运行结果:

this指针




(6)类中自带的函数


构造函数

:名字与类名一致,并且可以通过成员列表初始化参数;在创建对象时会默认生产且自动调用的函数,一般可用于初始化类成员等;因为默认的构造函数是没有任何操作的,此时需要我们去重写构造函数

class AA
{
public:
	//AA(){};  //普通构造函数
	AA(int num):number(num){} //构造函数 并使用成员初始化列表初始化成员数据            
	int number;
}:
/*成员初始化列表格式:在构造函数后加一个冒号然后把括号
里的数据初始化到成员中*/


拷贝构造函数

:函数名与构造函数相似,但是需要传递一个类型与本类一致的一个变量的引用,在创建对象时会自动创建一个拷贝构造函数,拷贝构造函数相当于构造函数的重载函数,默认的拷贝构造函数在调用时其实只是把另一个已存在的类里的值都赋值到这个类中(这里的拷贝为浅拷贝),在类内存在堆空间问题时就需要我们重写拷贝构造函数实现深拷贝

class AA
{
public:
	AA(){};  //普通构造函数
	~AA(){}; //析构函数           
	int number;
}:


析构函数

:析构函数的函数名与类名一致但要在前面加上 ~ 符,在创建的类对象会自动创建一个析构函数,且类对象被删除时会被自动调用,一般可用于类成员有申请堆空间内存时释放内存等,默认的析构函数是没有任何操作的,因此需要我们自己去重写析构函数

class AA
{
public:
	AA(){};  			//普通构造函数
	~AA(){}; 			//析构函数    
	AA(const AA &a){}; //拷贝构造函数       
	int number;
}:


注:

三个函数均无返回值,构造函数中支持成员参数初始化列表;且重写后的构造函数与析构函数,拷贝构造函数的权限必须为public

测试代码:

#include <iostream>

class AA
{
public:
        //构造函数
        AA(int num){
                std::cout << "调用了构造函数" << std::endl;
                this->a = new int(num); // 为a申请堆空间且初始化为10
        }
        
        //重写拷贝构造函数
        AA(const AA& b){	//这里的const可加可不加,默认加上最好
                std::cout << "调用了拷贝构造函数" << std::endl;
                this->a = new int(); // 为a申请堆空间且初始化为10
                *(this->a) = *(b.a);  //释放堆内存
        }

        //析构函数
        ~AA(){
                std::cout << "调用了析构函数" << std::endl;
                delete this->a;
        }
           void print(){
                std::cout << "a的值:" <<*(this->a) << std::endl;
                std::cout << "地址:" << this->a << std::endl;//打印一下地址
        }

private:
        int *a;
};

int main()
{
        AA a(10);
        AA b = a;

        b.print();
        a.print();

}
                                                                     
                                                                    

运行结果:

在这里插入图片描述

从运行结果中我们可以看到在创建对象a时,调用了构造函数,然后在创建对象b时调用的是拷贝构造函数,此时因为我们重写了拷贝构造函数(深拷贝),因此不会有任何问题,当我们把重写的拷贝构造函数注释掉后,我们会发现以下情况:

在这里插入图片描述

因为在拷贝构造函数中因为对象a中与对象b均带有一个指针,当我们使用默认的拷贝构造函数时,将会导致对象b里的成员变量指向的是对象a里申请的堆空间内存,它自己里面的成员变量是没有申请内存空间的;并且在对象a被删除后,析构时使得对象a中的堆空间内存被重复释放且b里面的成员变量也不能再去使用(会产生非法访问导致段错误),我们可以用一张图来解释深浅拷贝中的区别(图中的地址为假设的堆空间地址):

在这里插入图片描述




(7)类的is-a(public继承)关系与has-a(组合)关系

is-a关系是包含关系,has-a是拥有关系;如图:

在这里插入图片描述

①is-a(公有继承)关系我们举个简单的例子:就如我们把具有哺乳动物特效的动物称为哺乳动物,但哺乳动物是分为很多类的,有天上飞的,地上走的和水里游的;但是他们都具有哺乳动物的一种,因此我们可以说熊猫是哺乳动物的一种(is-a关系)

在C++中,is-a关系是通过基类与派生类(子类)间的继承实现的,派生类继承了基类后,派生类中就拥有了基类中的所有成员

其中继承的方式有三种:


private(私有继承)

:派生类private继承后,基类的成员在派生类中的权限均为private;派生类中想要使用只能通过调用方法来间接使用


protected(保护继承)

:派生类proteced继承后,public权限在派生类中变为protected权限,protected与private权限保持不变,派生类可直接使用


public(公有继承)

:派生类public继承基类后,在派生类中保持基类的中的成员权限
在这里插入图片描述


注:在继承与组合关系中

(1)构造函数的调用顺序为:先基类再派生类

(2)析构函数的调用顺序为:先析构派生类再析构基类

测试代码:

#include <iostream>

class AA
{
public:
        AA(int num):number(num){}
protected:
        int number;  
};

//public继承
class BB :public AA
{
public:
        BB(int num):AA(num){}
        void print(){
                std::cout << "a = " << number <<std::endl;    
        }
};

int main()
{
        BB a(10);
        a.print();

        return 0;
}

运行结果:

类继承

此时我们通过创建BB类的对象时,因为类BB继承于AA的类,因此BB类中与AA类属于is-a关系,因为创建BB的类对象时,BB类包含了AA类因此会调用AA类中的构造函数,所以我们需要重写BB类中的构造函数并在成员初始化列表中给AA类的构造函数传递一个值过去,

②has-a(组合)关系就如:我们现在有一个盒子,然后盒子里面放了一个苹果;苹果与盒子两者是完全不相关的东西,我们只是把他们放到一起去使用而已;has-a关系就是两个不相干的类,但是我们可以把它们放到一起去使用

测试代码:

#include <iostream>

class Apple
{
public:
        Apple(int num):number(num){}
        int number;  
};

//public继承
class Box {
public:
        Box(int num):a(num){}   //通过成员初始化列表传参给Apple类的构造函数
        Apple a; //定义一个类对象
        void print(){
                std::cout << "apple = " << a.number <<std::endl;    
        }    
};

int main()
{
        Box a(10);
        a.print();

        return 0;
}

运行结果:

类组合

在组合中,我们在一个类中定义对象时可以直接定义,但是在需要传递参数时需要通过构造函数把需要传递的参数传递过去,因为对象在创建时就会默认先调用构造函数




(8)类成员方法的默认参数

类中的成员方法我们在编辑代码时是可以预先给予成员变量一个默认值的,当设置了默认参数后外部可不传递参数过来,当外部传递参数进来时就会覆盖掉默认参数的值;默认参数可写在声明或实现处,但一般写在声明处

测试代码:

#include <iostream>

using namespace std;

class Stu 
{
public:
     	Stu(int num=0,string name="lisi")
               :number(num),name(name)
       {

       }

       void print(){
                cout << "名字:" << this->name   << endl;
                cout << "学号:" << this->number << endl;
        }
    
private:
        int number;
        string name;  
};

int main()
{
       Stu lisi;//使用默认参数
       //Stu lisi(1,"lisi");//使用外部传的参数
       lisi.print();

       return 0;
}

运行结果:

默认参数


注意:在默认参数中只能从右到左的默认参数,即上面的代码,如果后面的name没有给”lisi”这个参数,前面的num就不能给默认值,否则会报错




(9)抽象类

我们把定义为虚函数,但是没有实现赋值为0 的虚函数称为纯虚函数,类中具有纯虚函数的类称为抽象类,抽象类是不能创建对象的,因为其成员方法还没有实现;抽象类只能作为基类,由派生类继承后重写该虚函数,派生类中若没有重写基类中的纯虚函数也将不能创建对象

格式:

class Box {
public:
		//纯虚函数
        virtual void print() = 0;    
};



本文仅个人理解,若有错误的地方麻烦提醒一下;非常感谢



版权声明:本文为takashi77原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。