C++类和对象

类和对象

  • 一、类
    • 类的定义
    • 类的访问限定符及封装
    • 类的实例化
    • 类对象的存储方式
  • 二、this指针
  • 三、默认成员函数
    • 构造函数
    • 析构函数
    • 拷贝构造函数
    • 赋值重载
      • 运算符重载
      • 赋值重载
      • 前置++和后置++

一、类

类的定义

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

类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。

定义类的关键字有两个:

class : class定义的类,成员访问权限默认为private
struct :struct定义的类,成员访问权限默认为public

【注意】:
C++类和对象_第1张图片
1、类中成员函数声明和定义分开实现时,函数的定义需要通过域作用限定符::找到指定的类。指定类和::写在函数名前面,而不是返回类型前。

2、声明定义同时在类里面实现的函数会被默认加上inline。

类的访问限定符及封装

面向对象的三大特性:封装、继承、多态。
封装:隐藏对象的属性和方法的实现细节,仅通过对外公开接口来和对象进行交互。

C++实现封装的方式:用类将对象的属性与方法结合在一起,通过访问权限选择性地将接口提供给外部的用户使用。

C++类和对象_第2张图片

访问限定符说明】:

  1. public修饰的成员在类外可以直接被访问;
  2. protected和private修饰的成员在类外不能直接被访问,两者的区别仅体现在继承;
  3. 访问限定符的作用范围:从该访问限定符出现的位置开始到下一个访问限定符出现时为止;
  4. class的默认访问权限为private,struct为public(因为struct要兼容C)。

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

类的实例化

类的实例化 <=> 定义一个类对象 =>才开辟空间

类写出来后没有实例化定义对象时仅仅是个声明,没有开辟空间,所以不能直接使用类里public的变量和函数。 类就像是设计房子的图纸,实例化就是按照图纸去建造房子。

类对象的存储方式

类中的成员变量储存在对象中,成员函数则储存在公共的代码段,静态成员储存在静态区也不在对象中。即,对象中只存储非静态成员变量。

对象所占的内存大小: 类中非静态成员变量经内存对齐后的大小。
【注意】:空类的大小为1字节,编译器给了空类一个字节来唯一标识这个类。

二、this指针

C++编译器给每个非静态成员函数都增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作都是通过该指针去访问,这个指针就是this指针。

this指针的特性:

  1. this指针:类类型* this ,每个非静态成员函数的第一个形参都是隐含的this指针;

  2. this指针本质上是成员函数的一个形参,是对象调用成员函数时将对象地址作为实参传递给this形参,所以this指针不存储在对象里,而是存在函数的栈帧中;

  3. this指针不能在形参中显示写出来,也不能实参显示传递,一般都是由编译器通过寄存器自动传递,但是可以在函数内显示使用。

由于this指针的存在,我们知道了成员函数(以下均指非静态成员函数)为什么只能通过对象才能够调用,而成员函数也是对对象进行处理,this指针的存在体现了C++面向对象的特点,同时也实现了封装。

  

三、默认成员函数

每个类中都存在6个默认成员函数,这些默认成员函数我们不能主动实现时编译器会自动生成。
C++类和对象_第3张图片

构造函数

构造函数的主要任务是初始化对象,而不是创建对象。

构造函数的特性:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
 class Date
 {
  public:
      // 1.无参构造函数
      Date()
     {}
  
      // 2.带参构造函数
      Date(int year, int month, int day)
     {
          _year = year;
          _month = month;
          _day = day;
     }
  private:
      int _year;
      int _month;
      int _day;
 };

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

编译器自动生成的构造函数对内置类型不做处理,对自定义类型会去调用它自己的构造函数。
  

初始化列表: 以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。

Date(int year, int month, int day)
     : _year(year)
     , _month(month)
     , _day(day)
 {}

初始化列表是对象成员定义的位置。构造函数建议均使用初始化列表初始化对象,而函数体内部可以用来做检查,赋值等列表干不了的活。

【注意】:

  1. 每个成员变量在初始化列表中只能出现一次(因为初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    引用成员变量
    const成员变量
    自定义类型成员(且该类没有默认构造函数时)
  3. 成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关,所以建议初始化列表的定义顺序与类成员变量的声明顺序保持一致。

  
构造函数的实现及使用:

  1. 一般情况下,类中有内置类型成员,就需要自己实现构造函数,不能使用编译器自动生成的;
  2. 类中全部都是自定义类型成员,可以使用编译器自动生成的,不用自己实现;
  3. 默认成员函数的参数也是可以传缺省值的

  
默认构造函数: 不传参就可以调用的构造函数,包括:无参构造函数,全缺省构造函数和编译器自动生成的构造函数。一个类中只能有一个默认构造函数。
  

【注意】:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给缺省值。

析构函数

析构函数是在对象销毁时会自动调用去完成对象中资源的清理工作。

析构函数的特性:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
  4. 对象生命周期结束时,C++编译系统会自动调用析构函数。

默认析构函数对内置类型不做处理,对自定义类型会去调用它的析构函数,不过没有动态申请内存内置类型本来就不用处理,会随着栈区销毁而释放。

析构函数的实现:

  1. 一般情况下,有动态申请内存就需要显示实现析构函数来释放资源;
  2. 没有动态申请内存,只有栈区上的成员,不用写析构;
  3. 需要释放资源的成员都是自定义类型,不用写析构。
 ~Stack()  //示例
 {
	 if (_array)
	 {
		 free(_array);
		 _array = nullptr;
		 _capacity = 0;
		 _size = 0;
	 }
}

拷贝构造函数

拷贝构造函数:用于同类对象初始化创建对象。

浅拷贝:按内存存储的字节序完成拷贝。

拷贝构造函数的特性:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用(常用const修饰),使用传值方式编译器直接报错,因为会引发无穷递归调用。
  3. 类中如果没有涉及资源申请,拷贝构造函数是否写都可以;一旦涉及到资源申请,则拷贝构造函数一定要自己实现,否则就是浅拷贝。

C++语法规定了,自定义类型对象的拷贝必须要调用拷贝构造来完成拷贝。若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对内置类进行浅拷贝,对自定义类型会去调用它的拷贝构造。

// Date(const Date d)   // 错误写法:编译报错,会引发无穷递归
Date(const Date& d)  // 正确写法
{  		
	_year = d._year;
	_month = d._month;
	_day = d._day;
}


String(const String& s)  //拷贝构造 -- 深拷贝
 	: _str(new char[strlen(s._str) + 1])
 {
 	strcpy(_str, s._str);
 }

拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

赋值重载

运算符重载

C++引入了运算符重载,运算符重载是具有特殊函数名的函数,同样具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名为:关键字operator后面接需要重载的运算符符号。

函数声明:返回值类型 operator操作符(参数列表);

注意】:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1个,因为成员函数的第一个参数为隐藏的this

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

    // 这里需要注意的是,左操作数是this,指向调用函数的对象
 bool operator==(const Date& d) // bool operator==(Date* this, const Date& d)
 {
	return _year == d._year;
	    && _month == d._month
	    && _day == d._day;
 }

赋值重载

赋值运算符重载格式:

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值的情况
  • 返回*this :要符合连续赋值
Date& operator=(const Date& d)  //示例
 {
	if(this != &d)
	{
	     _year = d._year;
	     _month = d._month;
	     _day = d._day;
	}
        
	return *this;
 }

【注意】:赋值运算符只能重载成类的成员函数不能重载成全局函数,因为类内未显式实现编译器还会自动在生成一个,就会冲突。

前置++和后置++

// 前置++
 // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
 Date& operator++()
 {
	 _day += 1;
	 return *this;
}

 // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
 // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
 //而temp是临时对象,因此只能以值的方式返回,不能返回引用

 // 后置++:
 Date operator++(int)
 {
	 Date temp(*this);
	 _day += 1;
	 return temp;
 }

前置- - 和 后置- - 同理。

你可能感兴趣的:(C++语法,c++,开发语言)