C++ 类 、构造、 析构、 重载 、单例模式 学习笔记及练习

一、拷贝构造函数
 1、是一种特殊的构造函数,就是用一个已有的对象去构造其同类的副本对象,即对象克隆。
 class 类名
 {
  类名(类名& that)
  {
   对类成员挨个赋值
   ...
  }
 }
 

 


 练习:定义一个学生类,实现它的拷贝构造函数。
  Student stu2 = stu1; // 调用拷贝构造
  stu2 = stu1; // 调用赋值构造
 2、编译器会默认生成一个拷贝构造函数
  编译生成的拷贝构造函数默认会逐字节复制类中的每一个成员。
  如果在类A中有类B成员,会在类A的拷贝构造中自动调用类B的拷贝构造。
 3、程序员可以自定义拷贝构造来取代默认的拷贝构造。
  a、拷贝构造只能有一个,不能重载。
  b、一旦程序员自定义的拷贝构造,编译器就不再生成。
  c、在自定义的拷贝构造中能通过编码来实现成员的复制。
 
 4、一般情况下编译器生成的拷贝构造完全够用,不要轻易自定义构造。
 5、什么情况下调用拷贝构造:
  a、对象与对象赋值
  b、用对象与函数传参
  c、用对象当作返回值
 练习:已知有一个学生类
 class Student
 {
  char* name;
  char sex;
  short age;
 };
 Student stu1;
 Student stu2 = stu1;
 stu.name = new char[20];

 


 
 此类在使用过程中偶尔会发生段错误、数据丢失、内存泄漏,如何改进此类。

二、初始化列表
 1、是一种成员的初始化方式,在构造函数的大括号体前使用小括号对类的成员进行初始化的一种方式。
 class 类名
 {
  类名(参数列表):成员1(参数1),成员2(参数2)
  {
   
  }
 }

 


  a、参数列表可以解决构造函数的参数与成员重名的问题。
  b、参数列表会行于构造函数执行。
 2、如果类成员是数组,可以使用{}进行初始化。
 3、如果有成员是类,可以在初始化列表中显示调用构造函数。
 4、如果成员中有const成员、引用成员,必须使用初始化列表。
 5、类成员的构造顺序与初始化列表无关,而是与成员定义的顺序有关。
三、this指针
 1、相同类型的对象各自拥有独立的成员实例,彼此双共享一份成员函数,成员函数是如何知道谁在调用它。
 2、为了让成员函数知道是哪个对象在调用,并准确访问到对象的成员,编译器会自动为每个成员函数添加一个看不到的参数,这个参数就是指向调用对象的指针(this)。
 3、类中的所有成员函数都有this指针,包括构造、析构、拷贝构造等。
  只是构造中this指向的是正在被创建的对象。
 4、this指针默认情况下都是隐藏的(在成员函数中访问成员变量时自动就加上了),但也可以显示使用。
 5、使用情况使用this
  a、区分成员变量与参数
  b、把对象当作返回值与其它对象进行交互。
四、常对象与常函数
 1、创建对象时添加const关键字,这个对象就不可再修改,就有了常属性,就意味着整个对象中的所有东西都不能修改。
 2、常对象不能调用普通成员函数,调用成员函数就相当于把对象的this指针给了它,就会有被修改的风险。
 3、函数体前加const关键的叫常函数,常对象只能调用常函数,普通对象也可以调用常函数。
  常函数就相当于对this指针添加了cosnt属性。
 4、常函数与'非'常函数会形成重载不会冲突。
 5、如果有成员确实需要修改,它又需要被const修饰,可以对成员添加一个关键字mutable,这样即使常对象调用了常函数依然可以修改成员。
五、析构函数
 1、当对象被销毁时自动调用的函数叫析构函数,对象的整个生命周期中只能被调用一次,它是对象被销毁前的最后一个执行的动作。
 
class 类名
 {
  // 不能重载、只能有一个
  // 没有返回值、没有参数 
  ~类名(void)
  {
   
  }
 }

 

 2、编译器会默认产生一个析构函数,默认析构函数负责销毁能看的到的成员,如果有成员是类,会自动调用成员的析构函数,成员的析构过程与构行过程相反。
 3、析构函数虽然不能与重载,但可以自定义,有自定义析构函数默认析构就不会生成。
 4、当类中有析构造函数看不到的资源时(new/malloc)、有需要还愿的设置时(把打开的文件关闭/把获取到的数据保存),这时就需要自定义析构函数。
 练习:实现一个学生类,实现其构造、拷贝构造函数,当学生对象被销毁时自动写谁的stu.txt文件中,要保证常对象也能写入。
六、赋值构造
 1、赋值构造就是一个对象给另一个对象赋值的时候调用的函数。
  
stu2 = stu1; // 赋值构造
  Student stu2 = stu1;// 拷贝构造
  void func(Student stu);// 拷贝构造
  func(stu1);
  Student func(void) // 拷贝构造
  {
   return *this;
  }
  Student stu = func()

 

 2、赋值构造函数的格式
 void operator = (Student& that)
 {
 }
 // 可以与其它对象进行交互
 Student& operator = (Student& that)
 {
 }

 

 3、编译器会默认生赋值构造,它的功能与拷贝构造的功能一样,把对象A完全拷贝给对象B。
 4、赋值构造也拷贝构造的区别
   拷贝构造:使用对象A去创建出对象B(调用时对象B还末生成)。
   赋值构造:对象A与对象B都已经构造完成,此时B = A;
   如果对象中有常成员拷贝构造可以成功调用,但赋值构造不行。
 5、一般情况下默认的赋值构造基本够用的,除非有成员是指针,指向了额外的内存空间,这种情况下才需要自定义拷贝构造、赋值构造。
 6、自定义赋值构造
  a、确定赋值构造的格式
  b、防止自赋值
   int num = 1;
   num = num;
  c、释放旧资源
  d、分配新的资源
  e、拷贝新内容
  //f、代码利用(显式调用拷贝构造)
 
七、静态成员与静态成员函数
 1、类成员被static修饰后,就会存储在bss段(此段是由编译存放的而且大小固定),在程序中动态的创建对象时它的静态成员就无法创建,所有的类对象共享一个静态成员。
 2、静态成员只能在类中声明不能类中定义(必须在类外定义)。
  类型 类名::静态成员名;
 3、静态成员就是声明在类中的全局变量,在任何位置都可以使用
  类名::静态成员名进行访问。
 4、静态成员函数,类中的成员函数被static修饰后就变成了静态成员函数,所有对象共享一份静态成员函数。
 5、静态成员函数不会传递this指针,也就不能访问成员变量。
  不通过对象也能调用静态成员函数
  类名::静态成员函数(参数)
 
八、单例模式
 1、只能创建出一个对象的类,这种类就叫作单例类,这种模式就叫作单例模式。
 2、为什么需要单例模式,是为了提高安全性和稳定性的技巧。
  只允许存在唯一对象实例
  单例模式的商业应用:
   网站计数器
   日志管理系统
   连接池、线程池、内存池
 3、获取对象实例的专门方法
  a、全局变量的定义不受控制,能防君子不能防小人
  b、专门方法是类的一部分,"我是类型我做主",
  借助类禁止在外部创建对象,仅在类内部提供获取对象的接口。
 4、如何实现单例模式
  a、禁止在类外部创建实例,私有所有的构造函数 private
  b、类自己维护其唯一实例,
   静态成员变量 static 类名 instance;
   静态成员指针 static 类名* instance;
  c、提供访问该实例的方法,静态成员函数getInstance()
 5、饿汉单例模式
  不管是否需要对象都已经创建好了。
  优点:效率高、速度快、稳定。
  缺点:浪费资源,不管需不需要对象都已经创建好;
 6、懒汉单例模式
  当首次使用获取对象时才会真正创建出对象。
  优点:节约资源
  缺点:效率低,速度慢,不安全(多线程情况下)。
 
 
 
一、操作符函数
 在C++中,编译器有能力把一个由数据、对象和操作符共同组成的表达式,解释为对一个全局或成员函数的调用。
 该全局或成员函数被称为操作符函数,通过重定义操作符函数,可以实现针对自定义类型的运算法则,并使之与内置类型一样参与各种表达式。
二、双目操作符表达式
  成员函数
    形如L#R双目操作符表达式,将被编译器解释为 L.operator#(R)
    a-b+c == a.operator-(b).operator+(c)
  全局函数
    形如L#R的双目操作符表达式,将被编译器解释为 ::operator#(L,R)
    a-(b+c) == ::operator-(a,::operator+(b,c))
 练习:把赋值运算符尝试重载为全局的。
三、单目操作符表达式
  成员函数
    形如#O或O#的单目操作表达式,将被编译器解释为 O.operator#(),唯一的操作数是调用对象。
  全局函数
    形如#O或O#的单目操作表达式,将被编译器解释为 ::operator#(O),唯一的操作数是调用对象。
四、典型的双目运算符重载
 
成员函数
 Point operator /+-*%|^& (Point& that)
 {
  Point t; // 会调用无参构造
  t.x = x / that.x;
  t.y = y / that.y;
  return t; // 不能返回局部对象的引用,否则会出现悬空引用
 }
 注意:原对象的值不变,要产生一个临时的对象
 bool operator > < >= <= == != || && (Point& that)
 {
  
 }
 Point& operator += -= *= /=  (Point& that)
 {
 
  return *this; 
 }
 注意:运算符的重载要符合情理。

 

 全局函数
 可能会访问到参数的私有成员:
  1、把成员变成公开,但会破坏类的封闭性。
  2、把全局函数声明为友元(友元不是成员),
  3、不能在友元函数中直接访问成员变量。
 
Point operator + (Point& a,Point& b)
 {
  Point t(a.x+b.x,a.y+b.y);
  return t;
 }

 

五、典型的单目运算符重载
 
成员函数:
 前++/-- 
 Point& operator ++/-- (void)
 {
 }
 后++/--
 Point operator ++/-- (int)
 {
 }
 全局函数:
 前++/-- 
 Point& operator ++/-- (Point&)
 {
 }
 后++/--
 Point operator ++/-- (Point&,int)
 {
 }

 

六、输入、输出运算符重载
 输入、输出运算符不能重载为成员函数,只能是友元。
 
ostream& operator << (ostream& os,Point& p)
 {
 }
 istream& operator >> (istream& is,Point& p)
 {
 }

 

七、特殊的运算符的重载
 [] 下标运算符,可以把对象当作数组来使用。
 () 函数运算符,可以把对象当作函数来使用。
 -> 成员访问运算符,可以把对象当作指针来使用。
 * 解引用运算符,可以把对象当作指针来使用。
 new/delete 也可以进行重载,但不建议使用。
  new会自动调用重载的new函数再构造函数。
  delete会先调用析构再调用重载的delete函数。
 只有极个别的运算符的重载对于对象来说是有意义(>>,<<)
  常考的运算符重载:前++/--,后++/--
八、运算符重载的一些限制
 1、不能重载的运算符
  :: 作用域限定符
  . 成员访问运算
  .* 成员指针解引用
  ?: 三目运算符
  sizeof 字节长度运算符
  typeid 类型信息操作符
 2、运算符的重载改变不了运算符的优先级
 3、无法改变运算符的操作个数
 4、无法发明新的运算符
 5、重载运算符要注意运算符的一致性
  不要改变运算符默认的运算规则
 6、运算符的重载是为了方便使用、增强可读,不应该成功卖弄的工具。
练习:使用C++的语法实现单向链表,并重载[]、<<。
 
 
 
 
 
 
 
 
 
 
 
posted @ 2018-08-09 18:50  Scott9527  阅读(511)  评论(0编辑  收藏  举报