C/C++(2)

目录

(1)概念

(2)this指针

(3)构造函数、析构函数

(4)继承

1)什么是继承

2)父类与子类有相同成员

3)多重继承

(5)类成员的访问控制

1)定义与实现分开

2)public和private的使用

3)class与struct的区别

4)继承中的访问控制

(6)在堆中创建对象

1)我们可以在以下3处创建对象

2)C语言的内存分配和释放

3)new和delete

(7)引用类型

1)引用是变量的别名

2)引用类型与指针的区别

3)引用在函数参数传递中的作用

4)常引用

(8)面向对象程序设计

1)继承、封装

2)多态

(9)虚表

(10)运算符重载

(11)模板

(12)纯虚函数

1)概念

2)抽象类

(13)对象拷贝

1)拷贝构造函数

2)拷贝构造函数存在的问题

3)重载赋值运算符

(14)友元

(15)内部类

(16)命名空间

(17)static关键字

1)面向过程设计中的static

2)面向对象设计中的static数据成员

3)面向对象设计中的static成员函数

4)static的应用:设计模式 —— 单子模式


(1)概念

封装:把函数定义到结构体内部,函数可以使用结构体的其他成员

类:带有函数的结构体

成员函数:结构体里的函数,不占用结构体的内存空间

//成员函数不占用结构体的内存空间,因为它本不属于这个结构体
struct std{
   int x; int y; int m; int n;
   int plus()
   {
       return x+y;
   }
};

int main()
{
   int r = sizeof(a);    //输出结果r为16
   std a = {1,2,3,4};
      mov   dword ptr [ebp-10h],1     //结构体首地址
      mov   dword ptr [ebp-0Ch],2
      mov   dword ptr [ebp-8h],3
      mov   dword ptr [ebp-4h],4
   a.plus();
      lea   ecx,[ebp-10h]          //虽然函数无参数,编译器把this指针传给了函数
      call  plus
   return 0;
} 

(2)this指针

  • this指针:编译器把结构体的首地址作为参数传入成员函数
  • this指针是编译器默认传入的,通常使用ecx进行参数的传递
  • 成员函数都有this指针,无论是否使用,成员函数可以使用this指针来调用其他成员
  • this指针不能做++,--等运算,不能被重新赋值,this指针就是结构体首地址
  • this指针不占用结构体的宽度

(3)构造函数、析构函数

struct StdClass
{
   int x; int y; int m; int n;
   StdClass()         //编译器不要求一定要有构造函数
   {
      printf("与类名相同的函数,称为构造函数");
      printf("无返回值,void也不行");
      printf("构造函数可以有参数,也可以没有");
      printf("构造函数可以有多个,称为重载");
   }

   ~StdClass()        //编译器不要求一定要有析构函数
   {
      printf("和构造函数一样,无返回值");
      printf("析构函数只能有一个,不能重载");
      printf("不能有参数");
   }
};

int main()
{
   StdClass a;     //创建一个对象,构造函数会自动执行,主要用于初始化
   return 0;       //销毁对象时,析构函数执行,主要用于清理工作
}

析构函数何时执行:

  • 当对象在堆栈中分配(局部变量)

在main函数中创建对象,当main函数执行完毕,在return返回之前,析构函数会执行

  • 当对象在全局区分配(全局变量)

在应用程序(进程)退出之前,析构函数会执行

(4)继承

1)什么是继承

//继承就是数据的复制,减少重复代码的编写
struct person                                
{
   int age; int sex;         //person是父类、基类
};

struct teacher                    
{
   int age; int sex;         //未使用继承,则要重复编写代码
   int level; int classid;
};
struct teacher:person       //teacher是子类、派生类,teacher继承了person的代码
{
   int level; int classid;
};

2)父类与子类有相同成员

struct person                                
{
   int age; int sex;         
};
struct teacher:person                    
{
   int age; int classid;
};

int main()
{
   teacher t;
   int x = sizeof(t);        //输出结果x为16,说明只要继承,编译器就会帮我们重写代码
   t.age = 1;                //内存[ebp-10h]未赋值,说明编译器默认使用子类成员
      mov   dword ptr [ebp-8],1
   t.sex = 2;
      mov   dword ptr [ebp-0Ch],2
   t.classid = 3;
      mov   dword ptr [ebp-4],3
   t.person::age = 1;        //要使用父类的成员
      mov   dword ptr [ebp-10h],1
   return 0;
}

3)多重继承

//继承方式为空,默认为private
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>
{
<派生类类体>
};

struct x{int a; int b;};
struct y{int c; int d;};
struct z:x,y          //z同时继承了x、y,x和y的前后顺序决定了成员在内存中的顺序
{
   int e; int f;
};
int m = sizeof(z);    //输出结果为24

(5)类成员的访问控制

1)定义与实现分开

把定义写在头文件中,实现写在源文件中,是良好的编程习惯

2)public和private的使用

  • 结构体内部的函数才能访问private成员
  • public / private可以修饰函数和变量
  • 通过指针访问私有成员
struct test{
private:
   int x;
public:
   int y;
   void init(int x,int y)
   {
      this->x = x; this->y = y;
   }
};

test.t;
t.init(1,2);
int* p = (int*)&t;
printf("%d %d",*p,*(p+1));     //输出结果为1,2

3)class与struct的区别

成员访问区别:class默认类型为私有,struct默认类型为公有

4)继承中的访问控制

  • private继承方式,会把父类中的公有成员转成私有成员
  • 父类中的私有成员会被继承,子类不能直接访问父类私有成员

(6)在堆中创建对象

1)我们可以在以下3处创建对象

class person
{
private:   
   int x; int y;
pubilc:
   person()
   {printf("person()执行");}
   person(int x,int y)
   {
      printf("person(int x,int y)执行");
      this->x = x; this->y = y;
   }
   ~person()
   {printf("~person()执行");}
};

person p;                            //在全局变量区
void max()                           //在栈中
{
   person p;
}

int main()
{
   person* p = new person(1,2);      //在堆中创建对象,并调用构造函数
   delete p;                         //释放对象占用的内存,并调用析构函数
   return 0;
}

2)C语言的内存分配和释放

//C语言库函数
malloc(n);             //在堆中分配一块内存
free;                  //释放堆中内存空间


new 相当于 malloc + 构造函数
delete 相当于 free + 析构函数

3)new和delete

  • 用C和C++的方式在堆中申请int数组
int* p = (int*)malloc(sizeof(int)*10);              free(p);
int* p = new int[10];                               delete[] p;
  •  用C和C++的方式在堆中申请Class类型数组
int* p = (person*)malloc(sizeof(person)*10);     //malloc仅仅分配为对象分配空间      
free(p);                                         //仅仅释放空间

int* p = new person[10];                         //new为10个对象分配空间,并调用10次析构函数
delete[] p;                                      //释放空间,并执行10次析构函数
delete p;                                        //只释放一个对象空间,执行一次析构函数
  • 创建一个对象用:new 和 delete
  • 创建一个对象数组用:new [ ] 和 delete [ ]

(7)引用类型

1)引用是变量的别名

引用类型的变量定义时必须赋初值,只能指向一个变量

class base{
pubilc:
   int x;
};

int main()
{
   //基本类型
   int a = 10;
   int& b = a;
   printf("%d",x);

   //类
   base c;
   c.x = 1;
   base& d = c;
   
   //指针类型
   int*** e = (int***)1;
   int***& f = e;

   //数组
   int arr[] = {1,2,3};
   int (&p)[3] = arr;        //此时p是数组的别名
   p[0] = 4;
   printf("%d",arr[0]);      //输出结果为4

   return 0;
}

2)引用类型与指针的区别

int x = 1;     //必须初始化

int* p = &x;
int& a = x;

//运算
p++;
   mov   edx,dword ptr [ebp-8]
   add   edx,4
   mov   dword ptr [ebp-8],edx
a++;                                 //相当于x++
   mov   eax,dword ptr [ebp-0Ch]     //[ebp-0Ch]就是x的地址
   mov   ecx,dword ptr [eax]
   add   ecx,1
   mov   edx,dword ptr [ebp-0Ch]
   mov   dword ptr [edx],ecx

//赋值
p = (int*)1;
a = 100;

3)引用在函数参数传递中的作用

  • 基本类型
void plus(int& i)
{
   i++;return;
}

int main()
{
   int i = 10;
   plus(i); 
      lea    eax,[ebp-4]
      push   eax
      call   plus
      add    esp,4
   printf("%d",i);
   return 0;
}
  • 构造类型
struct base
{
   int x; int y;
   base(int x,int y)
   {
      this->x = x; this->y = y;
   }
};

void PrintByRef(base& ref,base* p)
{
   printf("%d %d",p->x,p->y);      //通过指针读取
   printf("%d %d",ref.x,ref.y);    //通过引用读取
   //指针可以进行赋值、运算
}

4)常引用

防止指针对变量进行错误修改

class base
{
public:int x;
};

void print(const base& ref)
{
   //ref = 100;  不能修改
   //ref.x = 200;  可以修改指向的内容
   printf("%d",ref.x);
}

int main()
{
   base b; b.x = 100;
   print(b);
   return 0;
}

(8)面向对象程序设计

1)继承、封装

  • 子类继承父类成员变量、成员函数
  • 数据成员一般设为private,成员函数设为public来当做接口,提供给他人调用
  • 辅助函数一般也设为private,不提供给他人使用
  • 可以在创建子类时,指定调用父类的哪个构造函数
class person
{
private:int age; int sex;
public:
   person()                   //无参的构造函数
   {

   }
   person(int age,int sex)
   {
      this->age = age;this->sex = sex;
   }
};

class teacher:public person
{
private:int level;
public:
   teacher(int level)                   //子类构造函数告诉编译器,调用父类无参的构造函数
   {
      this->level = level;
   }
   
   teacher(int level,int age,int sex):person(age,sex)        //子类的构造函数告诉编译器,使用有参的构造函数
   {
      this->level = level;
   }

    teacher(int level,int age,int sex)        //错误写法
   {
      person(age,sex)              //构造函数person将不会执行,编译器仍然执行无参的构造函数
                                   //不能显式调用
      this->level = level;
   }
};

int main()
{
   //声明子类时,会自动调用父类的构造函数,默认调用无参构造函数;没有构造函数则不调用
   teacher t(1); 
   
   //调用父类有参的构造函数,同时给3个成员赋值
   teacher t(1,2,3)
   return 0;
}

2)多态

  • 可以用父类对象的指针,访问子类对象的成员,但是只能访问被继承的成员
class A
{
   int x; int y;
};
class B:public A
{
   int z;
};

A* p = &B;    //指针p可以访问子类成员x、y,不能访问z
  • 函数重写:在父类和子类中有同名、同参数的函数
  • 多态可以让父类指针有多种形态(根据不同的对象来调用不同的函数),C++通过虚函数实现多态性
class person
{
private:int age; int sex;
public:
   virtual void print()           //虚函数
   {
      printf("%d %d",age,sex);
   }
   virtual void print() = 0;    //纯虚函数,只是为了提供统一的接口
                                 //父类有纯虚函数,子类一定要进行重写
                                 //含有纯虚函数的类称为抽象类,不能创建对象
                                 //虚函数可以直接使用,也可以被子类重载以后,以多态形式调用
};

class teacher:public person
{
private:int level;
public:
   void print()           //在子类中重写print
   {
      person::print();
      printf("%d",level);
   }
};

void MyPrint(person& p)   //子类、父类共用该函数
{
   p.print();             //多态:参数是父类的指针,但是仍然可以调用子类的函数
}

int main()
{  //构造函数的代码参考上一节,这里未写明
   //父类指针的多态,根据对象的类型不同,调用不同的函数
   person a(1,2);       //父类对象,调用父类的print
   MyPrint(a);
   
   teacher b(1,2,3);    //子类对象,调用子类的print
   MyPrint(b);
   return 0;
}

(9)虚表

  • 有虚函数就存在虚表,并且把虚表的地址存储在对象第一个内存单元处(4字节),第二个内存单元才开始存储数据成员
  • 父类有n个虚函数,父类的虚表存放的父类虚函数的地址;子类有n个虚函数,子类的虚表存放的子类虚函数的地址,不会存放父类虚函数的地址
  • 虚表存储的内容

子类未重写虚函数时

子类重写虚函数时

class A
{
public:
   int x;
   virtual void test()
   {
   printf("A");
   }
};

class B:public A
{
public:
   void test()
   {
   printf("B");
   }
};

void Fun(A* p)
{
   p->test();      //Fun函数体现了多态性
      mov    eax,dword ptr [ebp+8]      //把虚表的内存地址的地址存入eax
      mov    edx,dword ptr [eax]        //把虚表的内存地址存入edx
      mov    esi,esp
      mov    ecx,dword ptr [ebp+8]
      call   dword ptr [edx]            间接调用 //虚表中存储的是函数的地址,若调用第2个虚函数则是 [edx+4]
      
//若未使用虚函数,反汇编代码是
      call   A::test              直接调用(未添加关键字virtual)
}

printf("%d",sizeof(A));       //输出结果:有虚函数8字节,无虚函数4字节
                              //无论虚函数的个数,类的大小都只增加4个字节
                              //这4个字节就是虚表地址,所在的内存地址
printf("%d",sizeof(B));       //输出结果:8字节

(10)运算符重载

//用运算符实现一个函数的功能
class number
{
private:int x; int y;
public:
   number(int x; int y)
   {
      this->x = x; this->y = y;
   }

   bool max (number& n)                   //比较两个类的大小
   {
      return this->x > n.x && this->y > n.y;
   }

   bool operator> (number& n)             //运算符重载
   {
      return this->x > n.x && this->y > n.y;
   }
};

int main()
{
   number n1(1,1); number n2(2,2);
   bool r = n1.max(n2);
   bool r = n1 > n2;                      //此时>相当于max函数
}

(11)模板

  • 在函数中使用模板
//冒泡排序       
template <class M>                //模板用来替换其他类型
void sort(M* arr, int length)     //在调用时,M会被替换成相应的类型
{
   int i; int k;
   for(i=0;i<length-1;i++)
   {
      for(k=0;k<length-1-i;k++)
      {
         if(arr[k]>arr[k+1])
         {
         M temp = arr[k]; arr[k] = arr[k+1]; arr[k+1] = temp;
         }
      }
   }
}

int main()
{
   int arr1[] = {1,5,8,9,7,4};   //M被替换成int
   sort(arr1,6);                 //call   00401090
                                 //编译器重写了函数代码,因此调用地址不同

   char arr2[] = {1,5,8,9,7,4};
   sort(arr2,6);                 //call   0040D5F0
   return 0;
}
  • 在结构体 / 类中使用模板
template <class T, class M>
struct base                 //用T替换int,用M替换char
{
   T x; T y;
   M a; M b;
   T max()
   {
      if(x>y) return x;
      else{ return y;}
   }
   
   M min()
   {
      if(a<b) return a;
      else{ return b;}
   }
};

int main()
{
   base <int,char> t;   //创建一个对象
   t.x=1;t.y=2;t.a=3;t.b=4;
   int r = t.max();
   return 0;
}

(12)纯虚函数

1)概念

  • 虚函数:将成员函数声明为virtual
  • 纯虚函数:函数没有函数体(后跟 =0

2)抽象类

  • 含有纯虚函数的类,称为抽象类(Abstract Class)
  • 抽象类可以包含普通函数,但不能实例化
class bank
{
public:
vitual void max() = 0;   //纯虚函数
}

//不能实例化,即不能创建一个对象
bank b;
bank* b = new bank();     //编译器报错

(13)对象拷贝

1)拷贝构造函数

  • 拷贝构造函数:创建两个相同的对象,obj 和 objNew 两个对象相同
  • 拷贝构造函数在拷贝对象时,同样会拷贝父类的成员
class objecct
{
private: int x; int y;
public:
   object() {   }
   object(int x,int y)
   {
      this->x = x; this->y = y;
   }

   //编译器会默认添加构造函数,不需要深拷贝则不需要重写
   object(const object& obj)        //重写拷贝构造函数,拷贝构造函数参数是固定的
   {
    
   }
};

int main()
{
   object obj(1,2);
   object objNew(obj);     //拷贝构造函数
   object* p = new obj1(obj);
   return 0;
}

2)拷贝构造函数存在的问题

  • 若类中含有指针成员,拷贝构造函数在拷贝对象时,只复制指针的值,不复制指针指向的内容(浅拷贝
  • 两个对象中的指针成员指向了同一块内存,当第一个对象内存释放,第二个对象的指针成员将指向空的内存
  • 解决方法:重写拷贝构造函数

3)重载赋值运算符

  • 赋值运算符和拷贝构造函数存在同样的问题,都是(浅拷贝)
  • 如果有父类,要显式调用父类的重载运算符
class base
{
private: int x; int y;
public:
   base() {   }
   base(int x,int y)
   {
      this->x = x; this->y = y;
   }
   base& operator=(const base& ref)     //在父类中重载赋值运算符
   { ...  }
};

class sub:public base
{
private: int z;
public:
   sub() {   }
   sub(int x,int y,int z):base(x,y)
   {
      this->z = z;
   }
   sub& operator=(const sub& ref)        //要在子类中重载赋值运算符,因为父类的赋值运算符无法覆盖子类的成员
   {
//子类能全盘继承父类的成员,除了构造函数和析构函数,所以不能在函数体显式调用父类的拷贝构造函数
      base::operator=(ref);              //先执行父类的赋值运算符
      ...
      return *this;
   }
};

int main()
{
   base b1(1,2); base b2(3,4);
   b1 = b2;

   sub c1(1,2,3); sub c2(3,4,5);
   c1 = c2;      //使用赋值运算符,实现对象的拷贝(浅拷贝)
   return 0;
}

(14)友元

友元函数:在类中添加 friend + 函数原型,可以访问类中的任何成员

友元类:在类中添加 friend + class + 类名,可以访问类中的任何成员

破坏了C++的封装特性

(15)内部类

把一个类定义在另一个类内部,两个类没有任何关系

class outer
{
private: int x;
public:
   outer();              //构造函数
   class inner
   {
      private: int y;
      public:
         inner();        //内部类的构造函数
         void Fn();      //函数的定义
   };
};

printf("%d",sizeof(outer));      //输出结果为4
outer::inner c;                  //创建一个内部类的对象,必须处于public
                                 //若内部类处于private,则无法创建内部类对象

//内部类函数的定义与实现
outer::outer()  { ... }
outer::inner::inner() { ... }
void outer::inner::Fn() { ... }    //函数的实现

(16)命名空间

命名冲突问题

namespace n1{
   int x;
   void Fn() {...}
   class test
   {...};
   
}

namespace n2{
   int x;
   void Fn() {...}
   class test
   {
   public:
      void Fn() {...}
   };
}

void Fn() {...}         //未指定命名空间,默认为全局命名空间

use namespace n2;      //使用命名空间n2的成员可以不用添加前缀
int main()
{
   printf("%d",n1::x);
   printf("%d",n2::x);

   n1::test A;      //创建对象

   ::Fn();      //调用全局命名空间的函数
   n2::Fn();    //调用命名空间n2的函数
   Fn();        //直接调用,编译器将无法识别
   rerurn 0;
}

(17)static关键字

1)面向过程设计中的static


void Fun(int flag)
{
   static char buffer[10];                //buffer是只能Fun函数使用的全局变量,不再是局部变量
   if(flag)
   { strpy(buffer,"123456"); }
   else
   { printf("%s",buffer); }
}

int main()
{
   Fun(1);     //字符串123456放入buffer数组
   Fun(0);  
   Fun(0);
}

static相当于一个私有的全局变量  

extern用来声明函数可以省略,声明变量不能省略

2)面向对象设计中的static数据成员

  • 静态数据成员存储在全局数据区,且必须初始化:  数据类型 + 类名 + 静态数据成员名 = 值
  • 类的静态数据成员有2种访问形式:
  1. 类对象名 + 静态数据成员名           
  2. 类类型名 + 静态数据成员名
  • 静态数据成员可以避免名称冲突,实现信息隐藏
class base
{
private:
   int x; int y;
   static int z;         //z不在类的内存空间中,z只给base的成员使用,z是全局变量
};

int base::z = 0;   //静态成员初始化

int main()
{
   base c;
   base* p = new base();

   printf("%d",sizeof(base));    //输出结果为8

   return 0;
}

3)面向对象设计中的static成员函数

  • 只有类中的函数定义才能指定关键字static
  • 静态成员之间可以互相访问,包括函数与数据之间的访问
  • 普通成员函数可以访问静态成员函数和静态数据成员
  • 静态成员函数不能访问非静态成员函数和非静态数据成员
  • 调用类的静态成员函数的2种方式
  1. 类    名  +  静态成员函数名  + (参数表)
  2. 对象名  +  静态成员函数名  + (参数表)
  3. 指    针  +  静态成员函数名  + (参数表)
class base
{
private:
   int x; int y;
   static int z;              //静态数据成员
public:
   base(int x,int y);
   static int Getz();         //静态成员函数,只属于base的全局函数
};

int base::z = 0;              //静态数据成员初始化

base::base(int x, int y)      //实现构造函数
{
   this->x = x; this->y = y;
}

int base::Getz()              //实现静态成员函数,Getz函数不能访问成员x,y
{   return z;  } 

int main()
{
   base::Getz();              //可直接通过类名访问静态成员函数

   base c;
   c.Getz();                  //通过对象名访问

   base* p = new base();      //通过指针
   p->Getz();
   
   return 0;
}

4)static的应用:设计模式 —— 单子模式

实现定义的类中只有一个对象被创建,实现方式:

  1. 禁止对象随便创建
  2. 保证只存在一份对象
class A
{
private:
   A() {}                       //私有构造函数,无法直接创建对象
   static A* PInstance;         //静态成员,指向对象的指针
public:
   static A* GetInstance()      //通过类名调用GetInstance函数,并通过该函数创建对象
   {
      if(PInstance == NULL)
         PInstance = new A();         
      return PInstance;
   }
};

A* A::PInstance = NULL;      //静态成员初始化

int main()
{
   A* p1 = A::GetInstance();
   A* p2 = A::GetInstance();      //将创建同一个对象
   return 0;
}

 

 

(19集)

 

 

 

 

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值