Kaze
Kaze
Published on 2022-03-06 / 127 Visits
0
1

MOOC C++(一)

1. 从C到C++

  • 引用:

    • 类型名 & 引用名 = 某变量名。某个变量的引用,等价于这个变量,相当于该变量的一个别名。

      int n = 4;
      int & r = n;// r引用了n,则相当于n为r的别名,r和n之后就是一回事了
      
    • 引用只能引用变量,不能引用常量和表达式。

    • 初始化后它就一直引用该变量,不会再引用别的变量了。

    • 常引用:不能通过常引用去修改其引用的内容

      int n= 100;
      const int & r= n;
      r=200;∥编译错
      n=300;∥/没问题
      
  • const关键字:

    • 常量指针:
      1. 不可通过常量指针修改其指向的内容
      2. 不能把常量指针赋值给非常量指针(防止通过非常量指针修改常量指针指向的内容)
  • 动态内存分配:

    • P=new T;

      T是任意类型名,P是类型为T*的指针。动态分配出一片大小为 sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P。

    • 数组:P= new T[N];

    • 释放内存空间:

      delete 指针; //该指针必须指向new出来的空间

      delete [] 指针; //该指针必须指向new出来的数组

  • 内联函数:

    • 好处:可以提高函数执行的效率;坏处:可执行文件体积增大(以空间换时间

    • 在函数定义前面加“ inline”关键字,即可定义内联函数

  • 函数重载:

    • 函数名相同而参数不同
  • 函数的缺省参数(or默认参数):

    • C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。

    • void func( int x1, int X2=2, int X3=3){}
      func(10);∥等效于func(10,2,3)
      func(10,8);∥等效于func(10,8,3)
      func(10,,8);/不行,只能最右边的连续若干个参数缺省
      
    • 如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句未必需要使用新増的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。

  • 类和对象:

    • 通过类,可以定义变量。类定义出来的变量,也称为类的实例,就是我们所说的“对象”。

    • 和结构变量一样,对象所占用的内存空间的大小,等于所有成员变量的大小之和。

    • 类的成员函数和类的定义分开写

      class CRectangle{
      	public:
      		int w,h;
      		int Area();//成员函数仅在此处声明
      };
      int CRectangle::Area(){
      	return w*h;
      }
      
    • 类成员的可访问范围:如果某个成员前面没有关键字,则缺省地被认为是私有成员。

2. 类和对象基础

  • 构造函数:
    • 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数
  • 复制构造函数
    • 只有一个参数,即对同类对象的引用。
    • 如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能(浅拷贝)。
    • 对象间赋值并不导致复制构造函数被调用
    • 传入函数实参和函数返回对象时都会调用复制构造函数
  • 类型转换构造函数:
    • 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数
    • 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
  • 析构函数:
    • 名字与类名相同,在前面加 ~ ,没有参数和返回值,一个类最多只能有一个析构函数。
    • 析构函数在对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
    • 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
    • 对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。
    • delete运算会导致析构函数调用。若new一个对象数组,那么用delete释放时应该写[]。否则只delete一个对象(调用一次析构函数)
    • 函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用
    • new出来的对象在生命期结束后不会自动调用析构函数,需要手动delete

3. 类和对象提高

  • this指针
    • 其作用就是指向成员函数所作用的对象
    • 非静态 成员函数中可以直接使用this来代表指向该函数作用的对象的指针(静态不可以
  • 静态成员变量
    • 静态成员:在说明前面加了static关键字的成员。
    • 访问静态成员:类名::成员名
    • 必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过
    • 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
  • 常量成员函数
    • 常量成员函数执行期间不应修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。
    • void GetValue() const { }
    • 两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
  • 友元
    • 友元分为友元函数和友元类两种
    • 友元函数:一个类的友元函数可以访问该类的私有成员. 在函数前加上friend关键字声明友元函数。
    • 可以将一个类的成员函数(包括构造、析构函数)说明为另一个类的友元。
    • 友元类:如果A是B的友元类,那么A的成员函数可以访问B的私有成员。在类前加上friend关键字声明友元类。
    • 友元类之间的关系不能传递,不能继承。a是b的友元,b是c的友元,但a不是c的友元

4. 运算符重载

  • 运算符重载的基本概念:

    • 运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。

    • 运算符重载的实质是函数重载,可以重载为普通函数,也可以重载为成员函数。

    • 把含运算符的表达式转换成对运算符函数的调用。

    • 把运算符的操作数转换成运算符函数的参数。

    • 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

    • 重载为成员函数时,参数个数为运算符目数减一。重载为普通函数时,参数个数为运算符目数。

    • 返回值类型 operator 运算符(形参表) {
      
      }
      
  • 赋值运算符的重载:

    • 赋值运算符“=”只能重载为成员函数

    • String & operator = (const char * s){
      	if(this == &s){return *this};//防止a=a这样的赋值导致出错
      	delete [] str;
      	str = new char[strlen(s)+1];
      	strcpy(str,s);
      	return *this;
      }
      
  • 动态数组:

    • //用以支持根据下标访问数组元素,如n=a[i]和a[i]=4这样的语句(a为对象)
      int & CArray::operator [] (int i)
      {
      	return ptr[i];
      }
      //返回值为int不行,不支特a[i]=4
      
    • class CArray {
      private:
      	int size;//数组元素的个数
      	int* ptr;//指向动态分配的数组
      public:
      	CArray(int s=0);//s代表数组元素的个数
      	CArray(CArray & a);//复制构造函数
      	~CArray();//析构函数
      	void push(int v);∥用于在数组尾部添加一个元素v
      	CArray & operator= (const CArray & a);//用于数组对象间的赋值
      
      	int length(){return size;}/∥返回数组元素个数
      	int & operator[] (int i) //用以支持根据下标访问数组元素
      	{
      		return ptr[i];
      	}
      };
      
      CArray::CArray(int s):size(s)
      {
      	if (s==0) {ptr = NULL;}
      	else {ptr = new int[s];}
      }
      
      CArray::CArray(CArray & a)
      {
      	if (!a.ptr) {
      		ptr = NULL;
      		size = 0;
      		return;
      	}
      	ptr = new int[a.size];
      	memcpy(ptr,a.ptr,sizeof(int)*a.size);
      	size = a.size;
      }
      
      CArray::CArray()
      {
      	if (ptr) {delete [] ptr;}
      }
      
      //赋值号的作用是使"="左边对象里存放的数组,大小和内容都和右边的对象一样
      CArray & CArray::operator=(const CArray & a)
      {
      	//防止a=a这样的赋值导致出错
      	if (ptr==a.ptr) {return *this;}
      	//如果a里面的数组是空的
      	if (a.ptr==NULL){
      		if (ptr) {delete [] ptr;}
      		ptr =NULL;
      		size = 0;
      		return *this;
      	}
      	//如果原有空间够大,就不用分配新的空间
      	if (size < a.size) {
      		if (ptr) {delete [] ptr;}
      		ptr = new int[a.size];
      	}
      	memcpy(ptr,a.ptr,sizeof(int)*a.size);
      	size = a.size;
      	return *this;
      }
      
      //在数组尾部添加一个元素
      void CArray::push(int v)
      {
      	if (ptr) {
      		int* tmpPtr = new int[size+1];//重新分配空间
              memcpy(tmpPtr,ptr,sizeof(int)*size;//拷贝原数组内容
              delete [] ptr;
              ptr = tmpPtr;
          } else {ptr = new int[1];}
          ptr[size++]=v;//加入新的数组元素
      }
      
  • 自增、自减运算符的重载:

    • 自增运算符、自减运算符有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:前置运算符作为一元运算符重载;后置运算符作为二元运算符重载,多写一个没用的参数。
    • 前置运算符返回引用,后置运算符返回对象。
  • 运算符重载注意事项:

    1. C++不允许定义新的运算符;
    2. 重载后运算符的含义应该符合日常习惯;
    3. 运算符重载不改变运算符的优先级;
    4. 以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;
    5. 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。

Comment