【c++】类和对象

类和对象

面向过程和面向对象的初步认识

我们用军事为例,要完成一次作战,需要侦察、后勤保障、战略部署、战术部署...等等

  • 面向过程: 更加关注过程,关注如何侦察(无人机侦察、火力侦察、侦察小组侦察),如何后倾保障、如何战略部署、如何战术部署
  • 面向对象:更关注对象之间的关系,我们现在就可以找侦察参谋、后勤参谋、战术参谋、战略参谋,把他们都聚在指挥部里,而不是更关注过程

前言

在c语言中我们学习过struct,而在c++中,struct升级成了类。现在struct中不止能定义变量,还能定义函数。

类的定义

class 类名
{
   // 类体:由成员函数和成员变量组成
};//分号不能漏

类定义的两种方式

  • 声明和定义全部放在类体中, 成员函数在类中定义的话可能会被当做内联函数
class a
{
public:
    int add(int x,int y)
    {
    return x+y
    }
  int b;
  int c;
}
  • 声明和定义分离
//声明放在.h文件中
class a
{
public:
  int add(int x,int y);
  int b;
  int c;
}
//定义放在.cpp中
int a::add(int x,int y)
{
  return x+y;
}

推荐使用第二种定义方法

类的访问限定符及封装

访问限定符

访问限定符的种类

  • public(公有):可在类外直接被访问
  • protected(保护):不可在类外直接被访问
  • private(私有):不可在类外直接被访问

访问限定符的作用域

从该访问限定符出现的位置到下一访问限定符或者到类结束的位置

c++默认的访问权限

  • class的默认权限是private
  • struct的默认权限是public

class和struct的区别

  • 默认的访问权限不同
  • 在继承和模板参数列表位置有区别(现在还没学到,抱歉QAQ)

封装

  • 将数据和操作数据的方法有机结合,隐藏对象的属性和实现的细节,仅对外公开接口来和对象进行交互
  • 本质上是一种管理,让用户更方便使用类

类作用域

  • 类的所有成员都在类的作用域中,在类外定义成员时需要使用::作用于操作符指明成员属于那个类的作用域

类的存储

  • 成员变量存储在类中
  • 成员函数存储在公共代码区 因为每个成员变量都对应着一个对象,而函数只需要形参便能够实现其功能,不是哪个对象独有的,为了节省空间便存放在公共代码区(毕竟a0对象的加法函数和a1对象的加法函数一模一样,只是参数不同)

类的实例化

  • 用类类型创建对象的过程称作实例化。
  • 类是对对象进行描述用的,类似于设计图,定义一个类时并不会分配实际的内存空间来存储它,当实例化的时候才会。
  • 一个类可以实例化多个对象(根据一个设计图能够造出很多房子)

类对象模型

  • 一个类的大小就是其“成员变量”(不是“成员函数”)之和, 遵守内存对齐原则
  • 空类的大小是1字节,用来表示有这个类

this指针

  • 用来防止要修改a1对象的值却设置成了a2对象的值
  • C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

this指针的特性

【c++】类和对象_第1张图片
  • this指针的类型: 类类型* const 指针名,说明不能修改this指针
  • 只能在成员函数内部使用
  • this指针本质是成员函数的形参,而形参存储在栈区,所以this指针不存储于对象中
  • this指针是成员函数一个隐含的指针形参,一般由编译器通过ecx寄存器自动传递,不用用户传递(用户也没这资格)

类的六个默认成员函数

默认成员函数:用户没有显示实现,编译器会自动生成的成员函数。

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 负值重载函数
  5. 普通对象取地址
  6. const对象取地址

构造函数

  • 函数名与类名相同
  • 构造函数支持函数重载
  • 构造函数不带返回值
  • 类实例化时自动调用(防止忘记初始化)
  • 在声明周期内只调用一次
  • 主要任务是初始化对象而不是开空间创建对象
  • 以Date类举例
Date(int year, int month, int day)//日期类的构造函数
{
 if (month > 12 || month < 1 || (day > GetMonthDay(year,month) || day < 1))
 {
  cout<<"非法日期"< }
 cout << "Date(int year, int month, int day)" << endl;
 _year = year;
 _month = month;
 _day = day;
}
  • 如果类中没有显示构造函数,编译器会自动生成一个无参的默认构造函数,该构造函数对内置类型成员不会处理(有的编译器会处理,但不是标准行为),对于自定义类型会自动调用该自定义类型的构造函数。
  • c++11标准中,可以通过在声明内置成员变量时给缺省值来达到使用 编译器生成的默认构造函数对内置类型进行初始化。

默认构造函数的分类

  1. 无参构造函数
  2. 全缺省构造函数
  3. 编译器默认生成的构造函数

初始化列表

  • 构造函数体调用完之后,对象中有了个初始值,但实际上不能成为对对象中的成员变量初始化,构造函数体内的语句只能将其称为赋初值,因为初始化只能初始化一次,而构造函数体内可以多次赋值。
  • 初始化列表:以冒号开头,跟着逗号分隔数据成员列表,每个成员变量后面跟一个放在 括号中的初始值或表达式
  • 初始化列表还是成员函数定义的地方
  • 是一种构造函数
  • 以日期类举例
class Date
{
 public:
  Date(int year, int month, int day)
  : _year(year)
  , _month(month)
  , _day(day)
  {
  //函数体内容(可以不填)
  }
}
关于初始化列表注意事项
  • 每个成员变量在初始化列表中只能出现一次(因为初始化只能初始化一次)
  • 类中包含引用成员变量、const成员变量、自定义类型成员(且该类没有默认构造函数)
  • 尽量使用初始化列表初始化,因为对于自定义类型成员变量,一定会先使用初始化列表初始化
  • 成员变量的初始化顺序是由声明顺序决定的, 与在初始化列表中的出现顺序无关
  • 声明中的缺省值就是给初始化列表用的
  • 初始化列表可以和函数体结合起来用(比如malloc是否开辟成功的检查)
explicit关键字
  • 作用:用于修饰构造函数,防止单个参数或 除了第一个参数外其他参数都有默认值的构造函数发生隐式转换
  • 如果不用explicit修饰构造函数的话,Date d1 = 1为合法操作(1被隐式类型转换成Date的匿名对象,再调用d1的拷贝构造),加上则为非法操作了
class Date
{
 public:
  Date(int year, int month = 0, int day = 0)
  : _year(year)
  , _month(month)
  , _day(day)
  {
  //函数体内容(可以不填)
  }
}

析构函数

  • 函数名为:~类名
  • 无参数
  • 无返回值
  • 在对象生命周期结束的时候自动调用(防止我们忘记销毁对象)
  • 若类中无显式定义编译器会自动生成
  • 以Date类举例
~Date()//析构函数
{
 cout << "~Date()" << endl;
 _year = 0;
 _month = 0;
 _day = 0;
}
  • 同编译器生成的构造函数一样,只对自定义类型操作(调用该自定义类型的析构函数)
  • 当类中有申请内存这一行为时一定要写析构函数,此外就看心情了

拷贝构造函数

  • 只有一个形参(且该形参是本类类型对象的引用,通常会使用const修饰)
  • 在使用已有的对象创建新对象的时候自动调用
  • 是构造函数的一个重载形式
  • 对自定义类型的拷贝一定会调用拷贝构造
  • 参数一定要使用引用否则会引发无穷递归(原因就是上一点)
  • 若没有显示定义,编译器会默认生成拷贝构造。但该拷贝构造会对所有类型进行 浅拷贝,不只针对自定义类型, 浅拷贝十分容易造成野指针的问题(因为两个指针指向同一个空间)
  • 以日期类举例
Date(const Date& d)//拷贝构造
{
 _year = d._year;
 _month = d._month;
 _day = d._day;
}

拷贝构造的应用场景

  1. 使用已存在对象创建新对象
  2. 函数参数类型是类类型对象
  3. 函数的返回值类型是类类型对象

赋值运算符的重载

运算符类型的重载

  • 为了增加代码的可读性
  • 更为方便地操作自定义类型(比较,赋值,输入输出...等)
  • 语法:返回值类型 operator操作符(参数列表)
  • “.*”、“::”、“sizeof”、“?:”、“.” 这五个操作符不能重载
  • 不能通过连接其他符号创建新的操作符
  • 用于内置类型的运算符其含义不会改变
  • 在写运算负值重载函数的时候设计的参数要比实际少一个(因为this指针被隐藏了)
  • 以日期类举例
//
Date& Date:: operator+=(int day)
{
 int maxDay = GetMonthDay(_year, _month);
 _day += day;
 while (_day > maxDay)
 {
  _month++;
  _day -= maxDay;
  if (_month > 12)
  {
   _year++;
   _month = 1;
  }
 }
 return *this;
}
//
Date Date::operator+(int day)
{
 Date tmp = *this;
 tmp += day;
 return tmp;
}
//
Date& Date::operator-= (int day)
{
 while (day)
 {
  if (_day - day < 1)
  {
   day -= _day;
   _month--;
   if (_month < 1)
   {
    _month = 12;
    _year--;
   }
   _day = GetMonthDay(_year, _month);
  }
  else
  {
   _day -= day;
   break;
  }
 }
 return *this;
}
//
Date Date::operator-(int day)
{
 Date tmp = *this;
 tmp -= day;
 return tmp;
}
//
Date& Date:: operator++()//前置++
{
 _day++;
 if (_day > GetMonthDay(_year, _month))
 {
  _day = 1;
  _month++;
  if (_month > 12)
  {
   _month = 1;
   _year++;
  }
 }
 return *this;
}

Date Date::operator++(int)//后置++
{
 Date tmp = *this;
 ++(* this);
 return tmp;
}
Date& Date::operator--()//前置--
{
 _day--;
 if (_day < 1)
 {
  _month--;
  if (_month < 1)
  {
   _month = 12;
   _year--;
  }
  _day = GetMonthDay(_year, _month);
 }
 return *this;
}
//

Date Date::operator--(int)
{
 Date tmp = *this;
 --(*this);
 return tmp;
}
//
bool Date::operator>(const Date& d)
{
 if (_year > d._year)
 {
  return true;
 }
 else if (_year == d._year && _month > d._month)
 {
  return true;
 }
 else if (_year == d._year && _month == d._month && _day > d._day)
 {
  return true;
 }
 return false;
}
//
bool Date::operator==(const Date& d)
{
 return (_year == d._year && _month == d._month && _day == d._day);
}
//
bool Date::operator>=(const Date& d)
{
 return ((*this) > d || (*this) == d);
}
//
bool Date::operator<(const Date& d)
{
 return !((*this) > d || (*this) == d);
}
//
bool Date::operator<=(const Date& d)
{
 return (*this) < d || (*this) == d;
}
//
bool Date::operator!=(const Date& d)
{
 return!((*this) == d);
}
//
int Date::operator-(const Date& d)
{
 Date max = *this;
 Date min = *this;

 if (max < d)
 {
  max = d;
 }
 if (min > d)
 {
  min = d;
 }

 for (int i = 0; 1; i++)
 {
  if ((min += 1) == max)
  {
   return i+1;
  }
 }
}

赋值运算符的重载

  • 赋值运算符的重载格式:
    1. 参数类型为const T&
    1. 返回值类型是 T&
    1. 检测是否给自己复制
    1. 返回*this(否则不能实现连续赋值)
  • 赋值运算符只能重载成类的成员函数而不能重载成全局函数( 因为如果用户未显式定义,编译器会自动生成一个默认赋值运算符重载执行的是浅拷贝,如果定义成全局就会撞到)
  • 涉及到资源管理问题,赋值运算符重载 一定要写

赋值和拷贝构造的概念

  • 赋值:两个已经存在的对象进行拷贝
  • 拷贝构造: 通过一个 已经存在的对象去初始化一个 新的对象

const成员

  • 将const修饰的成员函数称为const成员函数
  • const实际上修饰的是 隐含的this指针,使得类中的任何成员都不能被修改
  • 用法:添加到函数的后面
void Print() const
  • 重载问题
1. void Print() const
2. void Print()

这两个函数构成重载,当我们将this展开,答案显而易见

1. void Print(const Date* this)
2. void Print(Date* this)

取地址以及const取地址操作符的重载

  • 这两个成员函数一般不需要定义,编译器会默认生成(使用的场景不多)
  • 想让别人获得指定内容的时候使用
  • 对于 只读函数,最好都加上const

static成员

  • 用static修饰的成员函数或成员变量被称为静态成员函数、静态成员变量
  • 静态成员变量、函数一定要在类外初始化(因为它们不独属于某一个对象,是公用的),应用案例:统计程序中累计创建了多少个类对象,使用了多少个类对象
  • 存放在静态区
  • 使用类名::静态成员 或者 对象.静态成员来访问
  • 静态成员函数没有隐藏的this指针
  • 静态成员受访问限定符的限制
  • 定义时不添加static关键字
  • 定义的例子
class Solution 
{
private:
    static int _ret;
    static int _i; 
};

int Solution::_ret = 0;
int Solution::_i = 1;

友元

  • 友元分为:友元函数和友元类
  • 友元是一种突破封装的方法,使用时需慎重(谨慎交友)

友元函数

  • 是定义在 类外普通函数,不属于任何类,但需要在类中声明一下(给好友发通行证)
  • 如果A是B类的友元函数,那么A能够访问B中的所有成员
  • 不能使用const修饰友元函数
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用原理和普通函数一样
  • 友元函数可以在类的定义中的任何地方声明,不受访问限定符的限制

友元类

  • 友元关系是单向的(你是我的朋友,我却不是你的朋友(悲))
  • 友元关系不能传递(朋友的朋友不是你的朋友)
  • 友元关系不能继承
class Solution 
{
friend A(int a);//友元的声明
private:
   int _ret;
   int _i; 
};
Solution s;
void A(int a)//友元函数
{
  printf(“%d”,s._i);
}

内部类

  • 定义在一个类里面的类就是内部类
  • 内部类天生是外部类的友元
  • 内部类可以直接访问外部类的static成员,不需要外部类的 对象/类名
  • sizeof(外部类)= 外部类,和内部类没有关系
class Solution 
{
public:
    class Sum
    {
        public:
        Sum()
        {
            _ret += _i;
            _i++;
        }
    };
    int Sum_Solution(int n) 
    {
        Sum a[n];
        return _ret;
    }
private:
    static int _ret;
    static int _i; 
};

匿名对象

  • 和临时对象一样,匿名对象 具有常性
  • 匿名对象的声明周期只有一行
  • const引用会延长匿名对象的生命周期
class Solution 
{
  Solution()
  {
  cout << "Solution" << endl;
  }
private:
   int _ret;
   int _i; 
};
Solution()//匿名对象,声明周期就只有定义的这一行

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