C++中的类和对象

目录

一、类的基本概念

类的大小计算

this指针

二、构造函数

特征:

初始化列表

三、析构函数

特征:

析构的顺序

四、拷贝构造函数

五、运算符重载

六、const修饰this指针

总结


一、类的基本概念

在C++中的类可以用struct或者class来定义,类里面不仅可以定义变量,也可以定义函数。他们的区别是struct默认权限是public而class的权限默认是private。

访问权限为private,说明只能在类的内部访问,无法在外面访问private成员,而public是都可以访问的。访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。

类里面的函数和声明是可以分开的,即声明在类里面,定义在类外面。

例如:

class test
{
public:
	int add(int a, int b);//类里面只给声明
};
int test::add(int a,int b)//在类外面定义
{
	return a + b;
}

类的大小计算

类中只存储成员变量,不存储成员函数,函数被放在了代码段。

如果有虚函数还得考虑虚函数表指针所占用的大小

例如:

class test
{
public:
	int add(int a, int b)
    {
	    return a + b;
    }
private:
	int num;
	char arr[19];
	int count;
};

std::cout<<sizeof(test)<<std::endl;//输出的大小为28

为什么不考虑函数?

一个类实例化出来的对象,每个对象的成员变量都可以存储不同的值,但是调用的函数却是同一个函数。函数被放在了代码段。

比如:

test t1;

test t2;

t1和t2调用里面的函数,每次调用都是同一个函数,如果访问里面的变量,则每个对象的成员变量都不同,所以,计算字节大小时,只关心里面的变量。

注意一个空类的大小为1(这个1只是为了占位)

this指针

在类里面有一个隐藏this指针。

class Student
{
public://访问权限
	void PrintStudentInfo()
	{
		cout<<_name<<" "<<_gender<<" "<<_age<<endl;
	}
private:
	char _name[20];
	char _gender[3];
	int _age;
};

PrintStudentInfo函数类之所以可以访问该类的私有成员变量,就是因为this指针的存在,相当于PrintStudentInfo(Student* this)。this指针存在栈上,因为是形参。

自己在形参里面加是不行的,但是可以在函数里面加

函数体相当于cout<<this->_name<<" "<<this->_gender<<" "<<this->_age<<endl;

class A
{
public:
	void Print()
	{
		cout << _a << endl;
	}
	void Show()
	{
		cout << "show" << endl;
	}
private:
	int _a;
};

A* p=nullptr;

p->Print();//崩溃

p->Show();//正常运行

原因:Print函数访问了成员变量,所以程序崩溃,但是Show并没有。


二、构造函数

构造函数在对象构造(实例化)时调用的函数,这个函数完成初始化的工作。

特征:

  • 函数名与类名相同
  • 无返回值
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数可以重载

自身没有定义构造函数时,C++编译器会自动生成无参默认构造函数。

默认构造函数分为三种

1、自己实现无参的构造函数

class test
{
  public:
    test()
    {    
        _tmp = 0;
    }
  private:
    int _tmp;
};

2、编译器默认生成的构造函数

3、自己实现的全缺省的构造函数

class test
{
  public:
    test(int tmp = 0)
    {    
        _tmp = tmp ;
    }
  private:
    int _tmp;
};

注意默认构造函数只能有一个,当自己实现了构造函数时,编译器就不会在默认生成一个构造函数

初始化列表

类里面有些变量在定义的时候必须初始化,此时得用初始化列表进行初始化。

三种需要初始化列表初始化的情况

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

很好理解,自定义类型的成员如果没有默认的构造函数,必须手动调用带参的构造函数。引用必须初始化。常量也得初始化。

class A
{
public:
	A(int a)
	:_a(a)
{}
private:
	int _a;
};

class B
{
public:
	B(int a, int ref)
		:_aobj(a)
		,_ref(ref)
		,_n(10)//成员变量定义的地方,进行初始化
	{
		_x=10;//不是这三种情况,可以初始化列表初始化,也可以在函数类初始化
	}
private://这三个必须在初始化列表初始化
	A _aobj; // 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const
	int _x;
	//成员变量声明的地方
};

注意:初始化列表中成员列出的顺序和它们在类中声明的顺序相同。


三、析构函数

析构函数是对象生命周期到了以后,自动调用,用作对象的清理。

特征:

1、析构函数是在类名前加上~

2、无参数返回值

3、一个类有且只有一个析构函数。若未定义,系统自动生成默认的析构函数

4、对象生命周期结束时,C++编译系统自动调用析构函数

5、析构函数可以在类外面定义

6、析构函数没有参数

析构的顺序

局部在栈上开辟的对象,按照栈的结构后进先出进行析构。堆上的对象调用delete就进行了析构。静态局部对象比局部对象生命周期长,比全局对象短,所以会比栈上的对象后析构,全局对象最后析构。

例子:

C c;
int main(void)
{
    A* a = new A();
    B b;
    static D d;
    delete a; 
}

定义顺序:c,a,b,d

由于 a先被释放,所以a先析构,b为局部对象,比 c,d先析构,接下来d析构,最后c析构。


四、拷贝构造函数

创建对象时,创建一个与之前创建的对象一模一样的新对象

特征:

1拷贝构造函数是构造函数一个重载的形式

2拷贝构造函数的参数只有一个且必须使用引用传参,否则会引起无穷递归

3、当没有自己写拷贝构造函数时,编译器也会默认生成

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Date()" << this << endl;
		
	}
    //拷贝构造函数
	Date(const Date& d)//注意是引用的方式
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{	
	Date d1(5,6,7);
	//这两种方式都是调用拷贝函数
	Date d2(d1);
	Date d3=d1;
	return 0;
}

需要注意的地方

1、首先拷贝构造函数不要写成下面这种形式

	Date(Date d)//会造成无穷递归
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
	}

这样会造成无穷递归,因为当对象传入拷贝构造函数时,相当于把对象拷贝给了d,这会导致又调用一次拷贝构造,最终变为无穷递归。

解决方法就是以引用的方式传参,不会拷贝。

2、Date d3=d1;这种方式也是调用的拷贝构造函数,而不是接下来要说明的 ‘=’ 的重载,这里很容易混淆。


五、运算符重载

传统的对类使用一些运算符操作例如==,是定义一个函数,对类里面的成员逐个比较。由于一些函数不够直观的展现出他的功能,想把自定义类型也可以用上运算符,所以引出运算符重载

关键字:operator

operator + 操作符 + 参数

举例:

operator = 的重载

Date& operator=(const Date& d)//增加引用为了避免调用拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;//目的:支持连续赋值
}

假如现在定义Date d1;Date d2;d2 = d1;这个过程也可以写成d1.operator=(d2)。本质上是d1把自己的this指针传进去了。可以想象成d1.operator=(&d1,d2)。

注意.* ::sizeof? :.  注意以上5个运算符不能重载。

        operator =也是编译器可以默认生成的。

扩展一下,这里其实还存在深浅拷贝的问题

如果不写拷贝构造函数或者赋值函数,编译器会自动生成拷贝构造和赋值函数,会完成按字节的值拷贝(逐字节)(浅拷贝

对于有些情况,浅拷贝是可以接受的(类里面的成员函数没有指针等),但是大多数情况还是不行的,需要自行完成深拷贝。

例如:一个stack类

class Stack
{
public:
	Stack(int capacity=10)
	{
		_arr = (int*)malloc(sizeof(int)*capacity);
		_size = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		free(_arr);
		_arr = nullptr;
		_size = _capacity = 0;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};
//假设使用默认的拷贝构造函数
int main()
{
	Stack st1(10);
	Stack st2(st1);
	return 0;
}

该程序会崩溃

原因:采用默认的拷贝构造函数会把st1内的类容逐字节拷贝给st2,但是st1中_arr是指向动态开辟的空间,st1中的_arr也会原封不懂得拷贝给_st2中的_arr。这样有很多问题,首先st1,st2中的_arr指向同一块地址,对其中所指向内容的改变也会导致另一个改变。除此之外更为严重的是当调用析构函数时,st1首先调用析构函数,先把_arr所指向的空间给释放掉,当st2调用析构函数时,也会对该空间在释放一次,所以程序运行到free这会崩溃。


六、const修饰this指针

看如下代码

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Date()" << this << endl;
		
	}
    //拷贝构造函数
	Date(const Date& d)//注意是引用的方式
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
	}
    void Print()
    {    
		cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
	int _day;
	int _month;
	int _year;
};

void fuc(const Date& d)
{
    d.Print();//会报错
}

fuc函数里面的d调用类里面的函数发现会出错:编译器显示不兼容类型限定符,对象类型是const Date。

原因:d是常属性的,相当于d.Print(&d),而类里面的*this并没有权限限制,所以权限提升了,从而会报错。

解决办法:

在函数后面加const,相当于const Date* this,const修饰*this。

    void Print()const
    {    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

总结

C++中总共六个默认成员函数

构造函数

析构函数

拷贝构造

赋值

普通对象与const对象取地址

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值