c++初级,c++类怎么理解

  c++初级,c++类怎么理解

  Yyds干货库存

  在写作之前,我们已经初步认识到类的内容,但那些是基本的。今天就分享一些细节,整理一下我的知识,比较难。所以,这个博客我可能写的不是很好。如果你有任何问题,请在评论区留言。我看到了会马上回复。如果我什么都不知道,我会帮你查资料。请理解。

  这个博客主要是和大家分享成员函数(方法)。里面有很多内容。让我一个一个来。让我们看一些例子作为今天的开始。

  请问这门课有什么内容?

  阶级人士

  {

  };

  这不就是一堂空课吗?里面什么都没有。如果你这么认为,那就有点简单了。是的,我们看到里面什么都没有,但实际上编译器会自动生成六个默认的成员函数,存在于这个类中。至于怎么验证,不用担心单机。

  我们先来看看这六个成员函数,不过我们只详细讲其中的四个,另外两个不是很重要。就简单说一下吧。

  构造函数完成对象的初始化。完成资源清理的不是构造函数的析构函数。构造函数的副本必须调用复制函数赋值重载重定义运算符。

  构造函数,也称为构造函数,帮助我们在C\记住,它们帮助我实例化对象。我们先来看看这个。

  阶级人士

  {

  公共:

  作废打印()

  {

  Cout 我的名字是 _name ,我今年 _age 岁 endl

  }

  空集合(const char* name,const int age)

  {

  strcpy(_name,name);

  _age=年龄;

  }

  私人:

  int _ age

  char _ name[20];

  };

  int main()

  {

  Person per1

  1.per1。集(《张三》,18);//每次都设置

  per1。print();

  返回0;

  }我们每次实例化一个对象,都要设置它。是不是有点太麻烦了?有时候我们可能会忘记初始化它,于是聪明的程序员就想,能否在对象实例化的时候,让编译器自动帮我们初始化,这样就不谈忘记了,于是一个特殊的函数——构造函数就出现了。

  构造函数的特征现在我们可以正式认识构造函数了。构造函数具有以下特征。

  没有返回值。记住返回值是yes还是no。void函数的名称与类名相同。当重载对象被实例化时,编译器自动调用相应的构造函数。我们先优化一下前面的代码,写一个构造函数。

  阶级人士

  {

  公共:

  作废打印()

  {

  Cout 我的名字是 _name ,我今年 _age 岁 endl

  }

  //带两个参数的构造函数(编译器的这个不算)

  人员(常数字符*姓名,常数年龄)

  {

  strcpy(_name,name);

  _age=年龄;

  }

  私人:

  int _ age

  char _ name[20];

  };

  int main()

  {

  Person per1(《张三》,18);

  per1。print();

  返回0;

  }

  默认构造函数是一种特殊的构造函数。需要先看下面,只是初步了解,以后再说。

  编译器自动生成不带参数的构造函数。所有默认构造函数都是由编译器自动生成的。总的来说,构造者可以说说上面的,但是对我们来说远远不够。我们需要明白一些事情。在第一段代码中,我想问一下类中是否有构造函数。这个问题我一开始就回答了。现有的编译器会自动生成构造函数。

  我们来验证一下。如果我们在类中写自己的构造函数,会发生什么?

  A级

  {

  公共:

  A(int a)

  {

  }

  };

  int main()

  {

  A _ a

  返回0;

  }

  我们开始疑惑,为什么没有写构造函数就报错了。我们一眼就能看出,我们在实例化对象时没有传递参数,这就是问题所在。从这里可以看出,编译器在实例化对象时会自动调用匹配的构造函数,也就是说,实例化对象时构造函数肯定会参与。

  但是看看下面的代码,为什么不报错?原因是编译器自动生成无参数构造函数。至于它的结构,没必要知道。

  B类

  {

  };

  int main()

  {

  B b

  返回0;

  }

  由此可以得出一个结论。如果我们不写构造函数,编译器会自动生成一个,但是如果我们写了,编译器不会。至于什么时候写,什么时候不写,先放在这里吧。在这里拓展还是有点困难。让我们把它放在最后。

  无参数构造函数。自己写的无参数构造函数也是默认构造函数。就说到这里吧。

  比较简单。至于代码里的问题,别急,我最后再说。

  A级

  {

  公共:

  答()

  {

  a=0;

  }

  作废打印()

  {

  Cout“无参数构造函数”endl

  cout a= a endl

  }

  公共:

  int a;

  };

  int main()

  {

  A a//为什么不写一个A()?

  a.print();

  返回0;

  }

  默认构造函数是最后一个。总的来说,我们对这里的默认构造函数了解很多,所以更容易理解。我们来看现象。

  A级

  {

  公共:

  //所有默认值

  a(整数a=10)

  {

  _ a=a

  }

  作废打印()

  {

  Cout“无参数构造函数”endl

  cout a= _ a endl

  }

  公共:

  int _ a;

  };

  int main()

  {

  A a//为什么不写一个A()?

  a.print();

  返回0;

  }

  哈,这是AA,要解决上面遗留的问题。说实话,这是语法规定的,我很难理解,但是我们可以通过现象来理解一些东西。

  先解编译器生成的。这里解决不了,也不要看这里的现象。记住就好。

  然后开始解自己写的无参数构造函数,我们来看现象。

  A级

  {

  公共:

  答()

  {

  }

  作废打印()

  {

  Cout你好,世界’endl;

  }

  };

  int main()

  {

  a a();

  a.print();

  返回0;

  }

  也就是说编译器找不到匹配的构造函数,或者不会报错。

  最后一个是完整的默认构造函数。

  A级

  {

  公共:

  //所有默认值

  a(整数a=10)

  {

  _ a=a

  }

  答()

  {

  }

  作废打印()

  {

  Cout“无参数构造函数”endl

  cout a= _ a endl

  }

  公共:

  int _ a;

  };

  int main()

  {

  a a();

  a.print();

  返回0;

  }

  这个错误和上面一样,只是我们不理解这个。默认不允许我们这么做吗?可以,但是这里不允许。

  你可能看我废话了半天。是的,我解释不清楚,但我可以知道一个这样的问题。

  如果一个类中有一个不带参数的构造函数,同时也有一个完全默认的构造函数,我们用A A();实例化一个对象,我想问一下编译器会用哪个构造函数,但是编译器不知道,干脆放弃这个用法,就结束了。

  我想和你谈谈默认构造函数的优先级。我不知道我的题名是否准确,也不知道这个知识点叫什么。来,我给你描述一下。

  如果一个类中有一个无参数的构造函数,同时也有一个完全默认的构造函数,我们用一个A;调用哪个构造函数?在这里用现象来得到答案。

  A级

  {

  公共:

  //所有默认值

  a(整数a=10)

  {

  Cout all default endl

  }

  //没有参数

  答()

  {

  Cout no reference endl

  }

  };

  int main()

  {

  A a

  返回0;

  }

  不好意思,这个会有误差,所以谈不上优先级。大家要记住这一点。无参数构造函数和全默认构造函数不能同时存在。我建议写全默认构造函数。

  我们前面提到了默认构造函数的作用。构造函数是用来为我们初始化的。但是这个初始化也有一个很大的问题。这里C\之前有一个很大的缺陷,直到C\ 11才弥补了一部分。

  不改内置C型的特点,很让人苦恼。使用默认构造时忽略内置类型有点不可接受。我们一眼就能看出来。

  A级

  {

  公共:

  答()

  {

  }

  私人:

  int _ a;

  double _ d;

  };

  int main()

  {

  A a

  返回0;

  }

  C 11填这个坑太大了。反正我对这个问题破口大骂。还好C\ 11填了这个坑,但是这种方法也会给初学者造成很大的困扰。先说方法。

  A级

  {

  公共:

  答()

  {

  }

  私人:

  int _ a=0;//在此声明

  double _ d=0.0

  };

  给这个初学者一个误解,int _ a=0;是的,但是打开空间是在实例化一个对象的时候,这只是一个声明,类似于默认函数。

  初始化自定义类型。上面的标题也不准确。可以说,对于自定义类型,编译器会调用自定义类的默认构造函数来帮助初始化。

  A级

  {

  公共:

  答()

  {

  Cout 协助初始化 endl

  }

  私人:

  int _ a;

  double _ d;

  };

  B类

  {

  公共:

  乙()

  {

  }

  A _ aa

  };

  int main()

  {

  B b

  返回0;

  }

  我们需要查看内部的结果,并再次调试。

  有些人可能会看到_aa的内容也没有初始化。这是因为我们没有在类A中写默认初始化,我来重写类A,调试一下。仅此而已。

  A级

  {

  公共:

  答()

  {

  //写到这里。

  _ a=0;

  _ d=0.0

  }

  私人:

  int _ a;

  double _ d;

  };

  从这里可以看出,我们需要初始化类中的内置类型,而不是自定义类型。

  说到这里,我们现在需要做一个结论。我们学过构造函数,知道默认构造函数,了解构造函数的作用,这些都是相当难的。

  析构函数如果构造函数是为了初始化,那么析构函数就是为了资源清理,这对于一些比较忘记的程序员来说是个福音。比如我们用malloc开辟一个空间,有时候很容易忘记free,会造成内存泄露。这样会造成一些问题。但是析构函数可以在对象的生命周期结束后自动调用这个析构函数。我们只需要在这个析构函数中自由。

  析构函数的特点我们先来看看析构函数的特点,这是我们的基础。

  析构函数名称前面有字符~。没有参数,没有返回值。一个类有且只有一个析构函数。如果没有明确定义,系统将自动生成默认的析构函数。在对象生命周期的末尾,C编译器系统自动调用析构函数。自动编译器调用我们先来看一个例子。

  A级

  {

  公共:

  a(整数上限=4)

  {

  int * arr=(int *)malloc(sizeof(int)* cap);

  断言(arr);

  a=arr

  memset(arr,0,sizeof(int)* cap);

  _ cap=cap

  }

  ~A()

  {

  _ cap=0;

  免费(a);

  a=nullptr

  }

  私人:

  int * a;

  int _ cap

  };

  int main()

  {

  A a

  返回0;

  }从上图我们可以知道,对象A的声明期结束后,编译器会自动调用析构函数完成资源清理。

  默认析构函数我们需要看看默认析构函数发生了什么,这可以帮助我们更高效地编写代码。

  析构函数会清理内置类型的资源吗?抱歉,它不能帮助我们清理内置类型。

  A级

  {

  公共:

  a(整数上限=4)

  {

  int * arr=(int *)malloc(sizeof(int)* cap);

  断言(arr);

  a=arr

  memset(arr,0,sizeof(int)* cap);

  _ cap=cap

  }

  私人:

  int * a;

  int _ cap

  };

  析构函数可以清理自定义类型吗?这是可能的,但是需要调用自定义类型的析构函数,这与默认构造函数的初始化是一样的。

  将调用自定义类型的析构函数。

  A级

  {

  公共:

  ~A()

  {

  自定义类型“endl”的“Cout”析构函数;

  }

  };

  B类

  {

  私人:

  A _ aa

  };

  int main()

  {

  B b

  返回0;

  }

  我们还可以看看如何调用析构函数。

  A级

  {

  公共:

  a(整数上限=4)

  {

  int * arr=(int *)malloc(sizeof(int)* cap);

  断言(arr);

  a=arr

  memset(arr,0,sizeof(int)* cap);

  _ cap=cap

  }

  ~A()

  {

  _ cap=0;

  免费(a);

  a=nullptr

  }

  私人:

  int * a;

  int _ cap

  };

  B类

  {

  公共:

  乙()

  {

  }

  私人:

  A _ aa

  };

  int main()

  {

  B b

  返回0;

  }总结这样,我们也可以得出一个结果。我们不需要为自定义类型编写析构函数,需要为内置类型清理资源,避免内存泄漏。

  复制构造创建对象时,能否创建一个与对象相同的新对象?复制构造是构造函数的一种,也是我们以后上课写作的重点内容。我们需要了解它。

  构造函数(Constructor):只有一个参数,这个参数是对这个类的一个对象(通常用const修饰)的引用,当用这个类的一个现有对象创建一个新对象时,由编译器自动调用。

  值的拷贝我们刚学函数的时候,大部分时候会给函数传入参数,也就是编译器会开辟另一个空间,把要传入传出的内容的拷贝放到这个空间里。这是价值观的简单复制。

  我们真的需要好好看看这个价值副本,我们发现它们的地址不一样。

  void func(int b)

  {

  cout a b endl

  }

  int main()

  {

  int a=10

  func(a);

  cout a a endl

  返回0;

  }

  对于一些简单的类型,这个副本就可以了,但现在我想给你看这个。

  void函数(int* pb)

  {

  免费(Pb);

  }

  int main()

  {

  int * arr=nullptr

  arr=(int *)malloc(sizeof(int)* 4);

  func(arr);

  免费(arr);

  返回0;

  }

  我们会发现一个问题。对于某些类型,简单的值复制根本不够。为什么会报告上述错误?原因是我们把数组名作为参数,编译器简单的把它作为指针复制到pb,但是pb指向的内容不变,所以我们把它免费放了两次,程序就会中断。

  复制构造的特点我们认可价值复制,现在可以说复制构造了。复制构造也是编译器默认生成的构造函数,其名称与类名相同。

  复制构造函数是构造函数的重载形式。复制构造函数的参数只有一个,而且必须通过引用传递,这样会导致无限的递归调用。先不说第二个话题,最后分享一下。

  默认副本构造我们先来看看默认副本构造是怎么工作的,看看什么时候需要自己写副本构造。

  A级

  {

  公共:

  A(int a=0,double d=0.0)

  {

  _ a=a

  _ d=d

  }

  ~A()

  {

  _ a=0;

  _ d=0.0

  }

  公共:

  int _ a;

  double _ d;

  };

  int main()

  {

  A _aa(1,3.0);

  cout aa。_a= _aa。_ a;

  cout aa。_d= _aa。_ d endl

  a _ bb(_ aa);

  cout _bb。_a= _bb。_ a;

  cout _bb。_d= _bb。_ d endl

  返回0;

  }

  这个构造可以说是一样的,所以我们不用担心编译器这次不会忽略内置类型。这很好,但也有问题。下面来看看。

  默认构造函数是值副本吗?这个问题很严重。我们应该知道,我们的对象将在生命周期结束时调用析构函数。如果这种情况发生两次,我想大家都会诅咒的。

  让我们拭目以待

  A级

  {

  公共:

  a(整数上限=4)

  {

  int * pa=(int *)malloc(sizeof(int)* cap);

  断言(pa);

  _ array=pa

  }

  ~A()

  {

  free(_ array);

  _ array=nullptr

  }

  公共:

  int * _ array

  };

  无效函数(A _bb)

  {

  cout _bb。_ array endl

  }

  int main()

  {

  A _ aa

  func(_ aa);

  cout _aa。_ array endl

  返回0;

  }

  这意味着默认情况下只生成一个简单的值副本,意味着后面的代码会被中断,同一个空间会被多次释放。

  int main()

  {

  A _ aa

  a _ bb(_ aa);

  返回0;

  }

  手写构造函数分享了这么多,好像我们还没有一个手写的构造函数。这张是常见的,但是里面有很多细节。

  A级

  {

  公共:

  A(int a=0,double d=0.0)

  {

  _ a=a

  _ d=d

  }

  A(常数A)

  {

  _ a=a. _ a

  _ d=a. _ d

  }

  私人:

  int _ a;

  double _ d;

  };

  我们开始挖掘细节。

  为什么用const装修好?我们不需要用const来修饰,但是有时候会写这样的代码。

  A(常数A)

  {

  a._ a=_ a//倒着写

  _ d=a. _ d

  }

  修改const可以避免这个错误,因为它不编译,但是可以很快发现问题。

  为什么用引用找到最重要的东西?首先要记住,要复制自定义类型,必须先调用构造函数。如果写的是普通的参考文献,也需要抄。编译器开始寻找构造函数,找到构造函数发现需要复制,寻找构造函数.有一个无限循环,所以我们必须使用别名来避免复制。

  如何处理构造函数的内置类型?我们已经详细讨论过这个问题了。这里有一个结论。如果你的类中没有这种类似的属性指向同一个空格,用默认的也可以,但是如果有,就需要自己写了。至于怎么写涉及到明暗抄写的知识,这里就不说了。这条规则适用于大多数情况。

  构造函数对自定义类型做了什么?我就不展示图表了。像构造函数和析构函数一样,构造函数寻找自己的构造函数。

  赋值运算符重载本来是分两部分写的,这也是很大的内容。我们可以为赋值操作符创建自己的实现规则。我们先来看看什么是赋值运算符重载。

  引入了运算符重载来增强代码的可读性。运算符重载是一个具有特殊函数名的函数,它也有自己的返回值类型、函数名和参数列表。它的返回值类型和参数列表与普通函数相似。

  原型:返回值类型运算符运算符(参数列表)

  为什么赋值运算符会重载?我们先来看一个案例。

  为什么会报错?我只是想让他们比较一下。我怎么了?但是编译器不允许。今天,我必须让它允许我。这就是赋值运算符重载出现的原因。

  上课日期

  {

  公共:

  日期(整数年=1900,整数月=1,整数日=1)

  {

  _year=年份;

  _month=月;

  _day=天;

  }

  私人:

  int _ year

  int _ month

  int _ day

  };

  int main()

  {

  日期d1(2022,5,18);

  日期d2(2022,5,18);

  如果(d1==d2)

  {

  cout == endl

  }

  返回0;

  }

  赋值运算符有很多重载,现在来看看如何使其合理。

  我们来看看这个函数。现在有一个问题。我们无法获得该类的属性。它被封装了。先把属性改成public,再解决这个问题。

  布尔运算符==(日期d1,日期d2)

  {

  //在这里,年、月、日是相等的。

  返回d1。_year==d2。_年

  d1。_month==d2。_月

  d1。_day==d2。_ day

  }这里就可以了,我们调用这个函数吧

  int main()

  {

  日期d1(2022,5,18);

  日期d2(2022,5,18);

  if(运算符==(d1,d2))

  {

  cout == endl

  }

  返回0;

  }

  我们可能会想,我可以随便取个函数名就把这个函数的函数写出来,而且还是那么花哨,但是你写的函数可以这样调用吗?但是我的也可以。

  如果(d1==d2)

  {

  cout == endl

  }

  这就是重载这个运算符的魅力所在。现在需要完善这个函数,参考一下。不需要开辟空间。用const修饰,避免被意外修改。

  布尔运算符==(常数日期d1,常数日期d2)

  {

  //在这里,年、月、日是相等的。

  返回d1。_year==d2。_年

  d1。_month==d2。_月

  d1。_day==d2。_ day

  }针对无法获取属性的问题,我给出两种解决方案。一种是在类中写一些GET函数来获取这些属性的值,另一种是使用friends。但是,这种方法会破坏封装,不推荐使用。

  在类中写入运算符重载。我们还不如直接把这个函数写在类里。简单快捷,避免破包。

  上课日期

  {

  公共:

  日期(整数年=1900,整数月=1,整数日=1)

  {

  _year=年份;

  _month=月;

  _day=天;

  }

  布尔运算符==(常数日期d1,常数日期d2)

  {

  //在这里,年、月、日是相等的。

  返回d1。_year==d2。_年

  d1。_month==d2。_月

  d1。_day==d2。_ day

  }

  公共:

  int _ year

  int _ month

  int _ day

  };

  你为什么报告这个错误?参数不是很对吗?我们之前说过,编译器默认会增加一个这个指针类型的参数,而且==是两个操作数,所以参数太多了。我们可以减少一个参数。

  boo operator==(const date d)//默认添加一个this指针

  {

  //在这里,年、月、日是相等的。

  return _ year==d. _ year

  _ month==d. _ month

  _ day==d. _ day

  }

  这个函数的调用就变成这样了。

  int main()

  {

  日期d1(2022,5,18);

  日期d2(2022,5,18);

  If (d1==d2) //d1==d2变成d1.operator==(d2)默认情况下

  {

  cout == endl

  }

  返回0;

  }

  代码里你说变成d1.operator==(d2),所以变成这样?你能证明什么?这里用对象的地址来证明。

  上课日期

  {

  公共:

  日期(整数年=1900,整数月=1,整数日=1)

  {

  _year=年份;

  _month=月;

  _day=天;

  }

  布尔运算符==(常数日期d)

  {

  cout“this”this endl;

  返回true

  }

  公共:

  int _ year

  int _ month

  int _ day

  };

  int main()

  {

  日期d1(2022,5,18);

  日期d2(2022,5,18);

  cout d1 d1 endl

  cout d2 d2 endl

  如果(d1==d2)

  {

  }

  返回0;

  }

  重载运算符=本来是想和大家分享一个date类的,但是如果此时此地写,至少需要5000字。我把它单独放在一个博客上,作为这几天我们学习课的小总结。在这节约会课上,你会发现我们上次讲过的所有知识点。这是一道小菜。这个很简单,目的是引出以下知识点。

  日期运算符=(常数日期d)

  {

  如果(这个!=d)

  {

  _ year=d. _ year

  _ month=d. _ month

  _ day=d. _ day

  }

  返回* this

  }

  先称之为。

  int main()

  {

  日期d1(2022,10,18);

  日期D2;

  d2=d1

  d2。print();

  返回0;

  }

  D2=d1细心的朋友可能会发现,我们写的是d2=d1我不想给d2赋值,而是想在这里重点关注它。我们通过调试来看看。

  这个电话是话务员超负荷。

  int main()

  {

  日期d1(2022,10,18);

  日期D2;

  d2=d1

  d2。print();

  返回0;

  }

  日期d2=d1该调用是复制构造,而不是操作符重载。

  int main()

  {

  日期d1(2022,10,18);

  日期d2=d1

  d2。print();

  返回0;

  }

  从这里可以得出下一个结论。如果我们在定义变量并初始化它们的时候调用copy构造,如果在赋值的时候已经定义了两个变量,我们称之为操作符重载。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: