【C++类和对象(中)】—— 我与C++的不解之缘(四)

简介: 【C++类和对象(中)】—— 我与C++的不解之缘(四)

前言:

接下来进行类和对象中的学习,了解类和对象的默认成员函数

一、类和对象默认成员函数

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

一个类,我们不显示实现的情况下,编译器就会默认生成一下留个默认成员函数。

       这里前4个(构造函数析构函数拷贝构造赋值重载)是重难点。

C++11以后还会增加两个默认成员函数,移动构造移动赋值

默认成员函数十分重要,从以下两个方面去深入了解:

  1. 我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求
  2. 编译器默认生成的函数不满足我们的需求,我们需要直接实现,那么如何自己实现呢?

       1.1、构造函数

       构造函数,是特殊的成员函数;这里需要注意,虽然名字叫做构造函数,但是构造函数的主要任务不是开辟空间创建对象(我们经常使用的局部对象是栈帧创建时,空间就已经开辟 好了),而是对象实例化时初始化对象

       这里构造函数本质上是替代实现的Stack和Data类中所写的 Init 函数,构造函数自动调用这一特点就完美替代了 Init 函数

1.1.1、构造函数的特点

构造函数的特点如下:

1、函数名和类名相同。

2、无返回值(返回值不需要写,void也不需要)。

3、对象实例化时系统会自动调用对应的构造函数。

4、构造函数可以重载

5、如果类没有显示定义构造函数,C++编译器会自动生成一个无参的默认构造函数;如果显示写了构造函数,编译器就不会再生成。

6、无参构造函数、全缺省构造函数、我们不写时编译器默认生成的构造函数,这三个都叫做默认构造函数。

7、我们不写,编译器默认生成的构造函数,对内置类型成员变量的初始化没有要求(是否初始看编译器);对于自定义类型成员变量,要求调用这个成员函数的默认构造函数初始化(如果这个成员变量没有默认构造函数,就会报错(这里要初始化这个成员变量,需要使用初始化列表来解决,后面会学习到))。

1.1.2、构造函数

       这里来看一下构造函数的前几个特点。

首先就是,构造函数的函数名和类名相同而且无返回值(不需要写返回值)

class Data
{
public:
  Data()
  {
    _year = 1;
    _month = 1;
    _day = 1;
  }
 
private:
  int _year;
  int _month;
  int _day;
 
};

       这里Data类里面的Data函数就是显示实现的构造函数(显示实现了构造函数,编译器就不会默认生成);

再来看,构造函数可以重载,我们就可以这样写:

class Data
{
public:
  Data()
  {
    _year = 1;
    _month = 1;
    _day = 1;
  }
  Data(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
 
private:
  int _year;
  int _month;
  int _day;
 
};

       这里两个构造函数就形成了重载,再创建对象时,不给参数就会调用第一个构造函数,给参数就会调用第二个参数。

最后,来看对象实例化时会自动构造函数:

#include<iostream>
using namespace std;
class Data
{
public:
  Data()
  {
    cout << "Data()" << endl;
    _year = 1;
    _month = 1;
    _day = 1;
  }
  Data(int year, int month, int day)
  {
    cout << "Data(int year, int month, int day)" << endl;
    _year = year;
    _month = month;
    _day = day;
  }
 
private:
  int _year;
  int _month;
  int _day;
 
};
int main()
{
  Data d1;
  Data d2(2006, 7, 20);
 
  return 0;
}

1.1.3、默认构造函数

默认构造函数有三种:

       无参构造函数全缺省构造函数和我们不写时编译器默认生成的构造函数

这三个函数有且只有一个存在(不能同时存在);

       这里虽然无参构造函数和全缺省构造函数形成函数重载,但是在函数调用时会存在歧义:

#include<iostream>
using namespace std;
class Data
{
public:
  Data()
  {
    cout << "Data()" << endl;
    _year = 1;
    _month = 1;
    _day = 1;
  }
  Data(int year = 1, int month = 1, int day = 1)
  {
    cout << "Data(int year, int month, int day)" << endl;
    _year = year;
    _month = month;
    _day = day;
  }
 
private:
  int _year;
  int _month;
  int _day;
 
};
int main()
{
  Data d1;
 
  return 0;
}

这里总结一下,编译传实参就可以调用的构造就是默认构造。

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

       编译器默认生成的构造函数,对于内置类型(整型,浮点型,字符类型,指针等)初始化没有要求,可能会初始化,也可能不做任何处理;对于自定义类型成员变量初始化会调用这个成员变量的默认构造函数(如果不存在默认构造就报错)。

       所以,大多情况下我们都需要自己实现构造函数。

       1.2、析构函数

       析构函数与构造函数的功能相反,析构函数不是完成对象本身的销毁(局部对象是存在栈帧的,函数结束栈帧就销毁了,局部对象就自动释放了);C++规定在销毁时会自动调用析构函数,完成对像中资源的清理释放工作。

       相关函数的功能类比之前 Stack 栈实现的DesTroy 销毁功能,对申请资源进行释放。

1.2.1、析构函数特点

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

2、无参数返回值(与构造函数一样,不需要加void)。

3、一个类只能有一个析构函数,如果没有显示定义,系统就会自动生成默认的析构函数。

4、对象生命周期结束时,系统就会自动调用析构函数。

5、与构造函数类似,我们不显示写,编译器默认生成的对内置类型不做处理,自定义类型就会调用它的析构函数。

6、这里需要注意,我们显示写了析构函数,对于自定义类型也会调用它的析构函数(也就是说,无论说明情况下,自定义类型都会自动调用析构函数。

7、如果类没有申请资源,析构函数可以不写,直接使用编译器生成的默认析构函数,就比如Data(日期类);如果默认生成的析构就满足我们的需求,不用写,比如MyQueue(用两个栈实现的队列);如果有资源申请,一定要自己写析构,否则就会造成资源泄漏,比如Stack(栈)。

8、一个局部域的多个对象,C++ 规定后定义的先调用析构

1.2.2、析构函数

自己实现析构函数,这里拿栈Stack类来举例:

class Stack
{
public:
  //构造函数
  Stack(int capacity = 4)
  {
    cout << "Stack" << endl;
    _arr = (int*)malloc(sizeof(int) * capacity);
    if (_arr == NULL)
    {
      perror("malloc fail");
      return;
    }
    _capacity = capacity;
    _top = 0;
  }
 
  //析构函数
  ~Stack()
  {
    cout << "~Stack" << endl;
    if (_arr)
      free(_arr);
    _arr = nullptr;
    _capacity = _top = 0;
  }
private:
  int* _arr;
  int _top;
  int _capacity;
};

       这里,对象生命周期结束时会自动调用析构函数,看一下释放真的调用了?

int main()
{
  Stack st;
 
  return 0;
}

1.2.3、自定义类型自动调用其析构函数

       对于自定义类型,无论我们写函数不写析构,都会自动调用其析构函数。

class Stack
{
public:
  //构造函数
  Stack(int capacity = 4)
  {
    cout << "Stack" << endl;
    _arr = (int*)malloc(sizeof(int) * capacity);
    if (_arr == NULL)
    {
      perror("malloc fail");
      return;
    }
    _capacity = capacity;
    _top = 0;
  }
 
  //析构函数
  ~Stack()
  {
    cout << "~Stack" << endl;
    if (_arr)
      free(_arr);
    _arr = nullptr;
    _capacity = _top = 0;
  }
private:
  int* _arr;
  int _top;
  int _capacity;
};
class MyQueue
{
public:
 
private:
  Stack pushst;
  Stack popst;
};
int main()
{
 
  MyQueue mq;
 
  return 0;
}

       

       这里可以看到,自定义类型构造和析构都调用了它的构造函数和析构函数。

       1.3、拷贝构造函数

       如果构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数(也就是拷贝构造函数是特殊的构造函数)。

1.3.1、拷贝构造的特点

1、拷贝构造函数是构造函数的一个重载。

2、C++规定,自定义类的对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。

3、拷贝构造的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归。

4、如果未显示定义拷贝构造,编译器会自动生成拷贝构造函数;

5、  像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完 成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。

       像Stack这样的类,虽然也都是内置类型,但 是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。

       像MyQueue这样的类型内部主要是⾃定义类型 Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现

MyQueue的拷⻉构造。

6、    传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤ 引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少 拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。

1.3.2、拷贝构造参数

       C++规定自定义类型对象拷贝时必须调用拷贝构造:

class Data
{
public:
  //构造
  Data(int year = 1 , int month = 1, int day = 1)
  { 
    cout << "Data(int year, int month, int day)" << endl;
    _year = year;
    _month = month;
    _day = day;
  }
  //拷贝构造
  Data(Data& d)
  {
    cout << "Data(Data&)" << endl;
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
private:
  int _year;
  int _month;
  int _day;
 
};
int main()
{
  Data d1(2006, 7, 20);
  Data d2(d1);
  Data d3 = d1;
  return 0;
}

       拷贝构造函数的第一个参数必须是类类型对象的引用,如果使用传值调用:

就会像下面这样,名称传参都会调用拷贝构造,调用完传参再次调用拷贝构造,无穷递归下去。

1.3.3、编译器默认生成的拷贝构造函数

       编译器默认生成的拷贝构造,对内置类型成员会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量就会调用它的拷贝构造函数。

二、赋值运算符重载

       2.1、运算符重载

1、当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。

2、运算符重载是具有特殊名字的函数,他的名字是由operator后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。

3、重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。

4、如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。

5、运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。

6、不能通过连接语法中没有的符号来创建新的操作符:比如operator@

7、.*    ::   sizeof   ?:   .   注意以上5个运算符不能重载。(选择题里面常考,要记一

重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int

operator+(int x,int y)

8、一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator+就没有意义。

9、  重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。

       C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

10、重载<<>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。

有了运算符重载,对于自定义类型的对象,我们就可以像内置类型那样,直接进行+/-/+=/-= 等操作。(要 根据自定义类的需求写)。

应用举例:

       这里以日期类为例,实现一个运算符重载 ==

//运算符重载
// == 
bool Data::operator==(Data& d)
{
  if (_year == d._year && _month == d._month && _day == d._day)
  {
    return true;
  }
  return false;
}

常见的运算符重载

       我们基本上可以重载所以的算术运算符、关系运算符和赋值运算符等,

算术运算符:+、-、*、/ ,用于自定义类型的算术运算。

关系运算符:==、!=、<、>、<=、>= ,用于自定义类型的比较操作。

赋值运算符:=,用于自定义类型的赋值操作。(当自定义类型(栈)包含动态分配的内存时,需要深拷贝以避免悬挂指针等问题。)

自增自减运算符:++、--,用于自定义类型的自增和自减操作。

下标运算符:[ ],用于自定义类型的数组或类似数组的操作。

流插入和提取运算符:<<、>>,用于自定义类型的输入输出操作。

函数调用运算符:(),允许自定义类型的对象像函数一样被调用。

成员访问运算符:->,一般 与智能指针或类似智能指针的类一起使用,用于访问指针所指向对象的成员。

前置++和后置++重载

C++规定

后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

前置++ 先使用再+1;而后置++是先+1再使用。

//前置++
Data& operator++();
//后置++
Data& operator++(int);

       2.2、赋值运算符重载

       赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。

1、赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成1.const 当前类类型引用,否则会传值传参会有拷贝

2、有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

3、没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷3贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。

4、  像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就4可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。

       像Stack这样的类,虽然也都是内置类型,但是 a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。

       像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载也不需要我们显示实现MyQueue的赋值运算符重载。

这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。

       现在就来哦实现日期类巩固这方面的知识。

       2.3、日期类的实现

Data.h:

#pragma once
#include<iostream>
#include<assert.h>
class Data
{
  friend std::ostream& operator<<(std::ostream& out, const Data& d);
  friend std::istream& operator>>(std::istream& in, Data& d);
public:
  //构造函数
  Data(int year = 1, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
 
  //析构
  
  //拷贝构造
  Data(Data& d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  //输出
  void Print();
 
  //运算符重载
  bool operator==(Data& d);
  bool operator!=(Data& d);
  bool operator<(Data& d);
  bool operator<=(Data& d);
  bool operator>(Data& d);
  bool operator>=(Data& d);
 
  Data& operator+=(int day);
  Data& operator+(int day);
  Data& operator-=(int day);
  Data& operator-(int day);
  int operator-(Data& d);
 
  Data& operator++();
  Data& operator++(int);
  Data& operator--();
  Data& operator--(int);
 
  //ostream& operator<<(ostream& out);
private:
  int _year;
  int _month;
  int _day;
};

Data.cpp:

#include"Data.h"
using namespace std;
 
//获得当前月份天数
int GetMonthDay(int year, int month)
{
  assert(month > 0 && month < 13);
  static int arr[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
  if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 40 == 0)))
  {
    return 29;
  }
  return arr[month];
}
//输出
void Data::Print()
{
  cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//运算符重载
// == 
bool Data::operator==(Data& d)
{
  if (_year == d._year && _month == d._month && _day == d._day)
  {
    return true;
  }
  return false;
}
// !=
bool Data::operator!=(Data& d)
{
  return !(*this == d);
}
 
// < 
bool Data::operator<(Data& 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 Data::operator<=(Data& d)
{
  return (*this) == d || (*this) < d;
}
// >
bool Data::operator>(Data& 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 Data::operator>=(Data& d)
{
  return (*this) == d || (*this) > d;
}
Data& Data::operator+=(int day)
{
  _day += day;
  while (_day > GetMonthDay(_year, _month))
  {
    _day -= GetMonthDay(_year, _month);
    _month++;
    if (_month == 13)
    {
      _month = 1;
      _year++;
    }
  }
  return *this;
 
}
// +
Data& Data::operator+(int day)
{
  Data tmp = *this;
  tmp += day;
  return tmp;
}
 
// -=
Data& Data::operator-=(int day)
{
  _day -=day;
  while (_day <= 0)
  {
    _month--;
    if (_month == 0)
    {
      _month = 12;
      _year--;
    }
    _day += GetMonthDay(_year, _month);
  }
  return (*this);
}
// - 天数
Data& Data::operator-(int day)
{
  Data d(*this);
  d -= day;
  return d;
}
 
// - 日期
int Data::operator-(Data& d)
{
  Data min(*this);
  Data max(d);
  if ((*this) > d)
  {
    min = d;
    max = *this;
  }int count = 0;
  while (min != max)
  {
    count++;
    min += 1;
  }
  return count;
  
}
//++
//前置++
Data& Data::operator++()
{
  (*this) += 1;
  return *this;
}
//后置++
Data& Data::operator++(int)
{
  Data d(*this);
  (*this) += 1;
  return d;
}
 
//前置--
Data& Data::operator--()
{
  (*this) -= 1;
  return *this;
}
//后置--
Data& Data::operator--(int)
{
  Data d(*this);
  (*this) -= 1;
  return d;
}
 
// <<
//ostream& Data::operator<<(ostream& out)
//{
//  out << _year << "年 " << _month << "月 " << _day << "日 " << endl;
//  return out;
//}
std::ostream& operator<<(std::ostream& out, const Data& d)
{
  cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
 
  return out;
}
//  >>
std::istream& operator>>(std::istream& in, Data& d)
{
  cout << "依次输入 年 月 日" << endl;
  in >> d._year >> d._month >> d._day;
  return in;
}

三、取地址运算符重载

       3.1、const 成员函数

1、 将const修饰的成员称之为从const成员函数,const成员放到成员函数参数列表的后面。

2、 const实际修饰该成员函数的this指针,表明在该成员函数中不能对类的任何成员进行修改。

3、 const修饰Data类的Print成员函数,

Print隐含的this指针由Data* const this 变为const Data* const this

#include<iostream>
using namespace std;
class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  // void Print(const Date* const this) const
  void Print() const
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  // 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩
  Date d1(2024, 7, 5);
  d1.Print();
  const Date d2(2024, 8, 5);
  d2.Print();
  return 0;
}

       3.2、 取地址运算符重载

       取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。

class Data
{
public:
  Data* operator&()
  {
    return this;
  }
  const Data* operator&()const
  {
    return this;
  }
private:
  int _year;
  int _month;
  int _day;
};

这里我们不想要访问到类对象的地址,也可以返回nullptr。

相关文章
|
1月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
45 0
|
1月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
115 0
|
3月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
115 12
|
4月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
4月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
4月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
5月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
98 16
|
5月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
4月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
237 6