C++语法笔记

笔记(C++)

本篇文章有部分的C++语法知识以及面向对象,涉及汇编底层。

文章目录

1. 编程语言的本质区别

编译型语言(不依赖虚拟机)脚本语言编译型语言(依赖虚拟机)
C、C++、object-CPHP、Python、JavaScriptJava、Ruby
  • C++ 代码可直接由编译器编译为汇编代码,再由汇编代码转到机器可识别的机器语言。(可以很好·地进行反汇编)
  • JS、PHP代码则只能由脚本引擎解析为中间代码,再由中间代码转化为机器语言。
  • Java代码则是由编译器编译为Class字节码,然后再由JVM虚拟机来转化为机器语言。

编程语言的产生是为了解决不同场景下的问题。

2. 函数重载

C语言不支持函数重载,C++支持函数重载

  • 函数重载的规则:
    1. 函数名相同;
    2. 参数类型不同、参数个数不同、参数顺序不同
  • 注意:
    1. 返回值类型与函数重载无关;
    2. 调用函数时,实参的隐式类型转换也可能产生二义性。
  • 本质:
    采用了name mangling 或者叫 name decoration技术:
  1. C++编译器默认会对符号名(比如函数名)进行改编、修饰,有些地方翻译为“命名倾轧”;
  2. 重载时会产生多个不同的函数名,不同编译器(MSVC、g++)有不同的生成规则。
#include <iostream>
using namemspace std;

int sum (int a, int b)
{
     return a + b;
}

int sum (itn a, int b, int c)
{
      return a + b + c;
}

double sum(int a, int b)
{
       return a + b;   //错误:重载与函数返回值类型无关
}


void display(long a)
{
       cout << a << endl;
}
void display(double a)
{
      cout << a << endl;
}

int main()
{
         display(1);   //int类型到long、double类型的隐式转换,有二义性。
        cout << sum(1, 2) << endl; //错误:有二义性,可能返回double型,也可能返回int型
        cout << sum(1, 2, 3) << endl; // 构成函数重载
        return 0;
 }

3. 默认参数

C++允许函数设置默认参数,在调用时可以根据情况省略实参(在使用默认参数时,与向函数传入实参的效果一模一样,都是把参数放进栈)。规则如下:

  1. 默认参数只能按照从右到左的顺序;
  2. 如果函数同时有声明、实现,默认参数只能放在函数声明中
  3. 默认参数的值可以是常量、全局符号(全局变量、函数名);
  4. 如果函数的实参经常是同一个值,可以考虑使用默认参数;
  5. 函数重载、默认参数可能会产生冲突、二义性(建议优先选择使用默认参数)
int age = 22;
void test()
{
        cout << "test()" << endl;
}
void display(int a = 11, int b = 22, int c = age, void (*func)() = test) //形参为全局变量、函数指针(指向函数(首地址)的指针变量,即本质是一个指针变量)
{
        cout << "a is " << a << endl;
        cout << "b is " << b << endl;
        cout << "c  is " << c << endl;
        func();
}
void display(int a, int b = 20)
{
       cout << "a is " << a << endl;
}
void display(int a)
{
        cout << "a is " <<  a << endl;
}

int main()
{
          display(10); //产生了二义性
          display();
          return 0;
 }

4. extern “C”

  • extern "C"修饰的代码会按照C语言的方式去编译;

第一种写法:

extern "C" void func()
{
       cout << "func()" << endl;
 }
 extern "C void func(int age)
 {
        cout << "func(int age)" << age << endl;
 }
//编译该函数时,编译器会报错,因为C语言不支持函数重载。

第二种写法:

extern "C" {
    void func() {
          cout << "func()" << endl;
     }
     void func(int age) {
          cout << "func(int age)" << age << endl;
     }
}
  • 如果函数同时有声明和实现,要让函数声明被extern "C"修饰,函数实现可以不修饰。
extern "C" void func();
extern "C" void func(int age);
/* 
extern "C" {
       void func();
       void func(int age);
}
*/
}
void func() {
     cout << "func()" << endl;
 }
 void func(int age) {
       cout << "func(int age)" << age << endl;
 }
  • 由于C、C++编译原则的不同,在C、C++混合开发时,可能会经常出现以下操作。
    C++在调用C语言API时,需要使用extern "C"修饰C语言的函数声明:请添加图片描述
    有时也会在编写C语言代码中直接使用extern "C",这样就可以直接被C++调用。

  • 在C++程序中,编译器会定义宏__cplusplus来区分C、C++环境。
    我们经常使用#ifnef、#define、#endif来防止头文件的内容被重复包含。请添加图片描述

  • #pragma once可以防止整个文件的内容被重复包含。
    区别:

  1. #ifnef、#define、#endif受C\C++标准的支持,不受编译器的任何限制;
  2. 有些编译器不支持#pragma once(较老编译器不支持,如GCC 3.4版本之前),兼容性不够好;
  3. #ifnef、#define、#endif可以针对一个文件中的部分代码,而#pragma once只能针对整个文件。

5. 内联函数(inline function)

  • 使用inline修饰函数的声明或者实现,可以使其变成内联函数。(建议声明和实现都增加inline修饰);
  • 特点:
    1. 编译器会将函数调用直接展开为函数体代码;
    2. 可以减少函数调用的开销;
    3. 会增大代码的体积。
  • 注意:
    1. 尽量不要内联超过10行代码的函数;
    2. 有些函数即使声明为inline,也不一定会被编译器内联,比如递归函数。
    3. 内联函数与宏,都可以减少函数调用的开销;
    4. 对比宏,内联函数多了语法检测和函数特性。
#define sum(x) (x + x) //宏定义

inline int sum(int x) { return x + x; } //内联函数

int a = 10; sum (++ a);
//当执行宏时,结果为24;(宏定义就是文本替换,相当于先++a,然后直接++a + ++a;)
//当执行内联函数时,结果为22。(执行++a + ++a)。

6. 表达式

在C++中,有些表达式是可以被赋值的:

int a = 1;
int b = 2; 
(a = b) = 3; //赋值给了a
(a < b ? a : b) = 4; //赋值给了b

7. const

  • const是常量的意思,被其修饰的变量不可修改;
  • 如果修饰的是类、结构体(的指针),其成员也不可以更改;
int age = 10;
const int * p0 = &age; //不能通过p0间接地修改age的值;
int const *p1 = &age; //同上
int * const p2 = &age; //不能修改指针p2的值;
const int * const p3 = &age; //既不能修改p3的值,也不能通过p3间接地修改age的值;
int const * const p4 = &age; //同上。
  • const修饰的是其右边的内容。

请添加图片描述

const修饰的指针,不能通过其去修改它内存的值。

8. 引用

  • 在C语言中,使用指针(Pointer)可以间接获取、修改某个变量的值;
  • 在C++语言中,使用引用(Reference)可以起到更指针类似的功能;
int age = 20;
//age就是一个引用
int &age = age;
  • 注意点:
    1. 引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用);
    2. 对 引用做计算,就是对引用所指向的变量做计算;
    3. 在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”;
    4. 可以利用引用初始化另一个引用,相当于某个变量的多个别名;
    5. 不存在【引用的引用、指向引用的指针、引用数组】。
  • 引用存在的价值之一:比指针更安全、函数返回值可以被赋值。
  • 引用的本质:
    1. 引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
    2. 一个引用占用一个指针的大小

数组的引用:

  • 常见的两种写法:
int array[] = {10, 20, 30};
int (& ref1)[3] = array;
int * const & ref2 = array; //数组名是数组首元素的地址,而首元素地址值是常量,非常量不能引用常量,所以要加 const

9. 常引用(const reference)

  • 引用可以被const修饰,这样就无法通过引用修改数据了,可以成为常引用;

  • const必须写在&符号的左边,才能算是常引用;

  • const引用的特点:

    1. 可以指向临时数据(常量、表达式、函数返回值等);
    2. 可以指向不同类型的数据;
    3. 作为函数参数时(此规则也适用于const指针):
      • 可以接受const和非const实参(非const引用,只能接受非const实参);
      • 可以跟非const引用构成重载
  • 当常引用指向了不同类型地数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量。
    即:

int age = 10;
const double & rage = age;
age = 18;
cout << age << endl; //age = 18;
cout << rage << endl; //rage = 10;

10. 类与对象

  • C++中可以使用·structclass来定义一个类
  • structclass的区别:
    1. struct的默认成员权限是public
    2. class的默认成员权限是private
//类的定义1
struct Person {
    //成员变量
    int m_age;
    //成员函数
    void run() {
       cout << m_age << "run()" << endl;
      }
}
//类的定义2
class Person {
public:
       //成员变量
       int m_age;
       //成员函数
       void run() {
           cout << m_age << "run()" << endl;
        }
}
int main()
{
       //访问person对象的成员变量
       Person person;
       //用person对象调用成员函数
       Person.my_age = 20; 
       person.run(); 
       
       //通过指针间接访问person对象
       Person *p = &person;
       p->m_age = 30;
       p->run();
 }
  • 上面代码中person对象、p指针的内存都是在函数的栈空间,自动分配和回收的

  • 全局变量在数据段、全局区(静态变量、常量也在全局区),局部变量在栈中,堆存放由程序员自由分配-释放的动态内存。

  • 对象的内存布局
    如果类中有多个成员变量,对象的内存的布局情况又是如何呢?

struct Person {
        int m_id;
        int m_age;
        int m_height;
        void display() {
               cout << "m_id is " << m_id << endl;
               cout << "m_age is" << m_age << endl;
               cout << "m_height is" << m_height << endl;
         }
}

请添加图片描述

11. this

  • this是指向当前对象的指针
  • 对象在调用成员函数的时候,会自动传入当前对象的内存地址:
struct Person {
       int m_id;
       int m_age;
       int m_height;
       void display() {
            cout << "m_id is" << this->m_id << endl;
            cout << "m_age is" << fhis->m_age << endl;
            cout  << "m_height is" << this->m_height << endl;
       }
}
  • 用指针访问对象成员的本质:
Person person;
person.m_id = 10;
person.m_age = 20;
person.m_height = 30;

Person *p = (Person *) &person.m_age; //person.m_age是int *型,要强转为Person *型
p->m_id = 40;
p->m_age = 50;

//将person对象的地址传入display的this
person.display(); 
//结果显示为:10 40 50(直接传m_age的地址值,依次在类所在内存空间找偏移量为0、4字节的内存单元)

//将p指针的存储的地址传给display的this
p->display();
//结果显示为:40 50 cc(传入m_age的地址值,从[&person + 4]、[&person + 4 + 4]、[&person + 4 + 4 + 4]的地方开始显示)

通过对象去调用成员函数,是将对象的地址值传入成员函数的this指针;
通过指针去调用,则是将指针的里面存储的地址传递给this指针。

  • 原理:如何用指针间接访问所指对象的成员变量
    1. 从指针中取出对象的地址;
    2. 利用对象的地址,和成员变量的偏移量计算出成员变量的地址;
    3. 根据成员变量的地址访问成员变量的存储空间。

当开辟函数栈空间时,编译器会初始化化栈内容为0xcccc(以免用户随意访问内存),“cc”对应汇编指令int 3,中断指令。

12. 封装

  • 成员变量私有化,提供共有的gettersetter给外界去访问成员变量。
struct Person {
private:
      int m_age;
public:
      void setAge(int age) {
             this->m_age = age;
       }
       int getAge () {
             return this->m_age;
        }
}

Person person;
person.setAge(20);
cout << person.getAge() << endl;

13. 内存

13.1 要点
  1. 调用函数、执行函数代码,其实就是CPU在访问代码区的内存、执行指令。
  2. 调用函数的时候,需要分配额外的空间来存储函数内部的局部变量。
  3. 代码区只能让CPU读,而不能更改,所以采用到其它内存空间。
  4. 局部变量存储在栈空间,函数代码存储在代码区。
13.2 内存空间的分布
  • 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域:
    1. 代码段(代码区)
      用于存放代码;
    2. 数据段(全局区)
      用于 存放全局变量等(整个程序运行过程中都存在);
    3. 栈空间
      每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间;
      自动分配和回收
    4. 堆空间
      需要主动去申请和释放

如下图所示:
请添加图片描述

13.3 堆空间
  • 在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存。
  • 回收堆空间内存:这块堆空间内存可以重新被别人使用(而不是清零)。
  • 堆空间的申请\释放:
1. malloc \ free
2. new \ delete
3. new [] \ delete []
  • 注意:

    1. 申请堆空间成功后,会返回那一段内存空间的地址;
    2. 申请和释放必须是1对1的关系,不然可能会存在内存泄露。
  • 现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在:

    1. 利:提高开发效率,避免内存使用不当或泄露;
    2. 弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手。
13.3.1 malloc \ free
int *p = (int *)malloc(4); //向堆中申请连续的4字节的空间,因为malloc函数返回的是申请出来 的堆空间的首地址,而且是void *型的指针,所以需要强制类型转化 为int *型;
*p = 4;
free(p); //释放申请出来的堆空间
char * p = (char *) malloc(4); //p存放内存申请出的堆内存中第一个字节地址
* p = 10;
* (p + 1) = 11; //p[1] = 11;
* (p + 2) = 12; //p[2] = 12;
* (p + 3) = 13; //p[3] = 13;
free(p); //将申请出来的空间全部释放

切记:申请出来的堆空间不能释放两次。

请添加图片描述

13.3.2 new \ delete
int  * p = new int; //向堆空间申请int大小的字节
* p = 10;
delete p; //new 和 delete 必须一一对应

char * p = new char;
* p = 10;
delete p;
13.3.3 new[] \ delete[]
char * p = new char[4];
delete [] p;

切记:当用new[]申请空间时,必须用delete[]释放空间(否则只会释放首地址的元素)。

13.4 堆空间的初始化
int  * p1 = (int *) malloc (sizeof(int)); //*p1未初始化
int  * p2 = (int *) malloc (sizeof(int));
memset(p2, 0, sizeof(int)); //将*p2的每个字节都初始化为0

int * p1 = new int; //未被初始化(由平台决定,可能被初始化)
int * p2 = new int (); //被初始化为0(调用memset函数初始化)
int * p3 = new int(5); //被初始化为5;
int * p4 = new int[3]; //数组元素未被初始化
int * p5 = new int[3](); //3个数组元素都被初始化为0;
int * p6 = new int[3]{}; //3个数组元素都被初始化位0;
int * p7 = new int[3]{ 5 }; //数组首元素被初始化为5,其他元素被初始化为0
13.4.1memset
  • memset函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法。
Person person;
person.m_id = 1;
person.m_age = 20;
person.m_height = 180;
memset (&person, 0, sizeof(person));

Person persons[] = {{1, 20, 180}, {2, 25, 165}, {3, 27, 170}};
memset(persons, 0, sizeof(persons));
  • 当传入数组首元素时,sizeof()运算符会计算整个数组的大小。

如微软文档上的介绍:

When the sizeof operator is applied to an object of type char, it yields 1.
When the sizeofoperator is applied to an array, it yields the total number of bytes in that array, not the size of the pointer represented by the array identifier. To obtain the size of the pointer represented by the array identifier, pass it as a parameter to a function that uses sizeof.

例:

#include <iostream>
using namespace std;

size_t getPtrSize( char *ptr )
{
   return sizeof( ptr );
}

int main()
{
   char szHello[] = "Hello, world!";

   cout  << "The size of a char is: "
         << sizeof( char )
         << "\nThe length of " << szHello << " is: "
         << sizeof szHello
         << "\nThe size of the pointer is "
         << getPtrSize( szHello ) << endl;
}

显示结果:

The size of a char is: 1
The length of Hello, world! is: 14
The size of the pointer is 4

14. 对象的内存

  • 对象的内存可以存在于 3种地方:
    1. 全局区(数据段):全局变量
    2. 栈空间:函数里面的全局变量
    3. 堆空间:动态申请内存(malloc、new等)
//全局区
Person g_person;

int main() {
     //栈空间
     Person person;

      //堆空间
      person * p = new Person; //指针p在栈中,申请出来的Person大小的内存位于堆中。
      return 0;
}

15. 构造函数(constructor)

  • 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作;

  • 特点:

    1. 函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数;
    2. 一旦自定义了构造函数,必须用其中的一个自定义的构造函数来初始化对象
  • 注意:通过malloc分配的对象不会调用构造函数

  • 默认情况下,编译器会为每一个类生成空的无参的构造函数。(错误结论)

  • 正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数。(在特定情况下,编译器为了优化代码,会省略构造函数)

15.1 构造函数的调用
struct Person {
     int m_age;
     Person () {
           cout  << "Person()" << endl;
     }
     Person(int age) {
          cout << "Person(int age)" << endl;
     }
 };


//全局区
Person g_p1;     //调用Person()
Person g_p2();   //这是一个函数声明,函数名叫g_p2,返回值类型是Person,无参
Person g_p3(20);  //调用Person(int)

int main() {
   //栈空间
    Person p1;     //调用Person()
    Person p2();    //这是一个函数声明,函数名叫p2,返回值类型是Person,无参
    Person p3(20);  //调用Person(int)

   //堆空间
   Person * p4 = new Person;  //调用Person()
   Person * p5 = new Person(); //调用Person()
   Person * p6 = new Person(20); //调用Person(int)
   return 0;
15.2 默认情况下,成员变量的初始化
struct Person {
      int  m_age;
};

//全局区(成员变量初始化为0)
Person g_p1; 

int main() {
 //栈空间(成员变量不会被初始化)
 Person p1;

//堆空间
Person * p2 = new Person;   //成员变量都不会初始化
Person * p3 = new Person();  //成员变量初始化为0
Person  * p4 = new Person[3]; //成员变量不会被初始化
Person * p5 = new Person[3](); //3个Person对象的成员变量都初始化为0
Person * p6  = new Person[3]{};  //3个Person对象的成员变量都初始化为0

return 0;
}
  • 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化。

  • 对象初始化:

Person() {
     memset(this, 0, sizeof(Person));
  }

16. 析构函数(Destructor)

  • 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作。
  • 特点:
    * 函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数。
  • 注意:
    * 通过malloc分配的对象free的时候不会调用析构函数
  • 构造函数、析构函数要声明为public,才能被外界正常使用
16.1 内存管理
struct Car {
      int m_price;
};
struct Person {
     int m_age;
      Car m_car;
};
Person p;

此代码内存示意图:
请添加图片描述

struct Car {
     int m_price;
 };
 struct Person {
      int m_age;
      Car m_car;
};
Person *p = new Person(); //指针p在栈空间,Person对象在堆空间

改代码内存示意图:
请添加图片描述

struct Car {
       int m_price;
 }
 struct Person {
       int m_age;
       Car *m_car;
       Perosn () {
             m_car = new Car(); //指针m_car在堆空间,Car对象也在堆空间
       }
       ~Personj () {
             delete m_car; //手动申请的堆空间,必须手动释放
        }
};
Person *p = new Person(); //指针p在栈空间,Person对象在堆空间

改代码内存示意图:
请添加图片描述

  • 对象内部申请的堆空间,由对象内部回收。
struct Car {
    int m_price;
 };
struct Person {
     int m_age;
     Car * m_car;
     Person () {
          m_car = new Car(); //Car对象在堆空间
     }
     ~Person () {
          delete m_car;
     }
};
Person p; //Person对象在栈空间

该代码内存示意图:
请添加图片描述

17. 声明与实现分离

17.1 声明

包括成员变量、成员函数都统一放在头文件里。
请添加图片描述

17.2 实现

成员函数实现的具体细节
请添加图片描述

18. 命名空间

  • 命名空间可以用来避免命名冲突;
namespace QY {
      int m_age;
      class Person {
       };
       void test() {
       }
}
int main() {
     QY::g_age = 20;
     QY::Person * p  = new QY::Person();
     QY::test();
     return 0;
//等效于:
     using namespace std;   //using QY::g_age;
     g_age = 20;
     Person * p = new Person ();
     test();
     return 0;
 }
  • 命名空间不影响内存布局。
18.1 命名空间的嵌套
namespace QY {
       namespace SS {
                int g_age;
        }
}
int main() {
       QY::SS::g_age = 10;
       
       using namespace QY::SS;
       g_age = 20;
       
       using QY::SS::g_age;
       g_age = 30;
       return 0;
}
  • 有个默认的全局命名空间,我们创建的命名空间默认都嵌套在它里面。
int g_no;

namespace QY {
       namespace SS {
                int g_age
         }
 }
 int main() {
       ::g_no = 20;
       ::QY::SS::g_age = 30;
       return 0;
 }
18.2 命名空间的合并
  • 以下两种写法是等价的:
//第一种:
namespace QY {
     int g_age;
}
namespace QY {
     int g_no;
}

//第二种:
namespace QY {
     int g_age;
     int g_no;
 }

请添加图片描述

18.3 其它编程语言的命名空间
  • Java
    • Package
  • Objective-C
    • 类前缀

19. 继承

  • 继承,可以让子类拥有父类的所有成员(变量 \ 函数)
struct Person {
      int m_age;
      void run () {
           cout << "Student::study()" << endl;
       }
};
struct Student : Person {
       int m_no;
       void study () {
             cout << "Student ::study()" << endl;
};

int main() {
   Student student;
   student.m_age = 20;
   student.m_no = 1;
   student.run();
   student.study();
   return 0;
  • 关系描述:
    • Student是子类(subclass,派生类)
    • Person是父类(superclass,超类)
  • C++中没有像Java、Objective-C的基类
    • Java:java.lang.Object
    • Objective-C:NSObject
19. 1 对象的内存布局

请添加图片描述

20. 成员访问权限

  • 成员访问权限,继承方式有3种:

    • public公共的,任何地方都可以访问(struct默认)
    • protected:子类内部、当前类内部可以访问
    • private:私有的,只有当前类内部可以访问(class默认)
  • 子类内部访问父类成员的权限,是以下2项中权限最小的那个

    • 成员本身的访问权限
    • 上一级父类的继承方式
  • 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限

  • 访问权限不影响对象的内存布局

  • class默认是私有继承,struct默认是公有继承(两者区别仅仅是默认访问权限问题)。

21. 初始化列表

  • 特点:
    • 一种便捷的初始化成员变量的方式;
    • 只能用在构造函数中
    • 初始化顺序只跟成员变量的声明顺序有关
  • 以下两种写法是等价的:
struct Person() {
       int m_age;
       int m_height;
       Person(int age, int height) : m_age(age), m_height(height) {
       }
};

struct Person {
        int m_age;
        int m_height;
        Person(int age, int height) {
            this->m_age = age;
            this->m_height = height;
        }
};

在C++中,当函数的声明有默认参数时,函数的实现就不能写默认参数。

int myAge() { return 10; }
int myHeight() { return 120;}
struct Person {
   int m_age;
   int m_height;
   Person(int age, int height) : m_height(myHeight()), m_age(myAge()) {
   }
};
Person p(20, 180);

返回值为:10、170;

struct Person {
     int m_age;
     int m_height;
     Person(iint age, int height) : m_height(height), m_age(m_height) {
     }
};
Person p(20, 180);

返回值为:
m_age = 未知;
m_height = 180;

21.1 初始化列表与默认参数配合使用
struct Person {
   int m_age;
   int m_height;
   Person(int age = 0, int height = 0) : m_age(age), m_height(height) { }
};
int main() {
   Person person1;
   Person person2(18);
   Person person 3(20, 180);
   return 0;
}
  • 如果函数声明和实现是分离的:
    1. 初始化列表只能写在函数的实现中;
    2. 默认参数只能写在函数声明中。
21.2 构造函数的互相调用
struct Person {
   int m_age;j
   int m_height;
   Person() : Person (00) { }     //传入的this指针指向初始对象
   Person (int age, int height) : m_age(age), m_height(height) {}
  };
  • 注意:下面程序写法是错误的,初始化的是一个临时对象。
struct Person {
    int m_age;
    int m_height;
    Person() {
        Person(0,  0); //在Person构造函数中重新又创建了一个Person对象,但是传入的this指针没有指向它。
    }
    Person (int age, int height) : m_age(age), m_height(height) {}
 };
21.3 父类的构造函数
  • 子类的构造函数默认会调用父类的无参构造函数

实例:

#include <iostream>
using namespace std;
struct Person {
      int m_age;
      Person () {
           cout << "Person::Person" << endl;
     }
};
struct Student : Person {
       Student () {
              cout << "Person::Student" << endl;
      }
};
int main () {
       Student student;   //会调用Person、Student的构造函数
       Person person;     //只调用Person的构造函数
       return 0;
}

结果:

Person::person
Student::student
Person::person
  • 如果子类的构造函数显示地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数

实例:

struct Person {
      int m_age;
      Person () {
           cout << "Person::Person ()" << endl;
       }
       Person(int age) {
           cout << "Person::Person(int age)" << endl;
       }
 };
 struct Student : Person {
       int m_no;
       Student::Person (20) { //显式地调用父类的有参构造函数
             cout << "Student :: Student()" << endl;
       }
};

结果:

Person :: Person(int age)
Student :: Student()
  • 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数。(因为子类的构造函数默认调用父类无参构造函数,现在也不调用有参的构造函数,父类的成员变量得不到初始化)
    实例:
struct Person {
      int m_age;
       Person(int age) {
           cout << "Person::Person(int age)" << endl;
       }
 };
 struct Student : Person {
       int m_no;
       Student::Person (20) { //显式地调用父类的有参构造函数,如果不调用就会报错
             cout << "Student :: Student()" << endl;
       }
};
21.4 继承体系下的构造函数示例
class Person {
      int m_age;
      Person() : Person(0) {
      }
      Person(int age) : m_age(age) {
      }
 };
 class Student : Person {
      int m_no;
      Student() : Student(0, 0) {
      }
      Student(int age, int no) : Person(age), m_no(no) {
      }  //子类的构造函数调用父类的构造函数去初始化父类的私有成员变量
};
21.5 构造、析构顺序
struct Person {
     Person() {
        cout << "Person :: Person() " << endl;
     ~Person() {
         cout << "Person :: ~Person() " << endl;
     }
};
struct student::Person {
      Student() {
         // call Person::Person()
         cout << "Student :: Student()" << endl;
       }
       ~Student() {
          cout << "Student :: ~Student()" << endl;
          // call Person::~Person()
          }
 };
 int main() {
      {
             Student student;
       }
       getchar();
       return 0;
 }

22. 父类指针、子类指针

  • 父类指针可以指向子类对象,是安全的(父类指针能访问的成员,子类都可以访问),开发中经常用到(继承方式必须是public
  • 子类指针指向父类对象是不安全的。
struct Person {
    int m_age;
 };
 
 struct Student : Person {  //默认公有继承,当为私有继承时,会报错
     int m_score;
 };

实例1:

  Person * p  = new Student(); //编译器认为p是指向Person对象
  p->m_age = 10;

请添加图片描述

实例2:

Student * p = (Student *) new Person(); //编译器认为p指向的是Student对象
p->m_age = 10;
p->m_score = 100; //将m_age的后面四字节直接赋值

请添加图片描述

23. 多态

  • 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
  • 多态是面向对象的一个非常重要的特性。
    • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
    • 在运行时,可以识别出真正的对象类型,调用对应子类中的函数
  • 多态的要素:
    • 子类重写父类的成员函数(override)
    • 父类指针指向子类对象
    • 利用父类指针调用重写的成员函数
23.1 虚函数
  • C++中的多态通过虚函数(virtual function)来实现;
  • 虚函数:被virtual修饰的成员函数;
  • ** 只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数**(也就是说子类中可以省略virtual关键字)。
23.2 虚表
  • 虚函数的实现原理是虚表,这个虚表里面存储这最终需要调用的虚函数地址,这个虚表也叫虚函数表
class Animal {
public:
    int m_age;
    virtual void speak() {
         cout << "Animal :: speak()" << endl;
     }
     virtual void run() {
         cout << "Animal :: run()" << endl;
     }
 };
 class Cat : public Animal {
 public:
       int m_life;
       void speak() {
             cout << "Cat ::speak() " << endl;
        }
        void run() {
             cout << "Cat ::run() " << endl;
        }
};


Animal * cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
23.2.1 Cat对象的内存分布图(X86环境下)

请添加图片描述

所有的Cat对象(不管在全局区、栈、堆)共用同一份虚表。

23.2.2 虚表汇编分析

请添加图片描述
虚表的作用:编译器在编译程序时,只能根据指针类型判断对象所属类的类型,然而当有虚函数后,在堆中申请的对象内存中自动绑定虚表的首地址,从而可以调用对象的真实的成员函数。

class Animal {
public:
    int m_age;
    virtual void speak() {
         cout << "Animal :: speak()" << endl;
     }
     virtual void run() {
         cout << "Animal :: run()" << endl;
     }
 };
 class Cat : public Animal {
 public:
       int m_life;
       void speak() {
             cout << "Cat ::speak() " << endl;
        }
        void run() {
             cout << "Cat ::run() " << endl;
        }
};


Animal * cat1 = new Cat(); 
Animal * cat2 = new Cat();
return 0;

上图代码中的cat1和cat2对象共用同一张虚表
请添加图片描述
父类里面声明为虚函数,子类重写就一定有虚函数。

23.3 调用父类的成员函数实现
class Animal {
public :
       virtual void speak() {
             cout << "Animal :: speak() " << endl;
        }
};
class Cat :: public Animal {
public :
      void speak() {
            Ainmal :: speak(); //调用父类的成员函数实现speak()
            cout << "Cat :: speak()" << endl;
        }
 };
23.4 虚析构函数
  • 如果存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数)
    • delete父类指针时,才会调用子类的析构函数,保证析构的完整性(父类析构函数为虚函数,子类析构函数也为虚函数)
      请添加图片描述
23.5 纯虚函数
  • 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范

  • 抽象类(Abstract Class

    • 含有纯虚函数的类,不可以实例化(不可以创建对象)
    • 抽象类也可以包含非纯虚函数、成员变量(只要有一个纯虚函数,就是抽象类)
    • 如果父类是抽象类,子类没有完全重写纯虚函数,那么这个子类依然是抽象类
class Animal {
      virtual void speak () = 0;
      virtual void walk () = 0;
 };
class Dog : Animal {
     void walk () {
         cout << "Dog :: walk()" << endl;
     }
 };
 class Hasiqi : Dog {
       void speak() {
         cout << "Hasiqi :: speak() " << endl;
       }
 };
 int main() {
      Animal animal; //Animal为抽象类,不能实例化
      Dog dog; //Dog为抽象类
      Hasiqi hasiqi; //Hasiqi继承Dog的成员函数,已经重写了Animal的成员函数,不为抽象类
      return 0;
 }

24. 多继承

  • C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)
class Student {
 public:
        int m_score;
        void study() {
             cout << "Student::study()" << endl;
        }
 };
 class worker {
 public :
      int m_salary;
      void work() {
            cout << "worker::work() " << endl;
      }
};
class Undergraduate: public student, public Worker {
public :
      int m_grade;
      void play() {
            cout << "Udergraduate :: play()" << endl;
      }
 };
 Udergraduate ug;
 ug.m_score = 100;
 ug.salary = 2000;
 ug.m_grade = 4;
 ug.study();
 ug,work();
 ug,play();
 }

改程序的内存示意图:
请添加图片描述

24.1 多继承体系下的构造函数调用
class Student {
   int m_score;
public:
   Student(int score) {
          this->m_score = score;
     }
};
class Workerj {
    int m_salary;
publc:
    worker(int salary) {
         this->m_salary = salary;
   }
};
class Udergraduate : public Student, public Worker {
public:
      undergaduate(int score, int salary) : Student(score), Worker(salary) {
      } //利用父类的构造函数去初始化父类私有成员变量
};
24.2 多继承——虚函数
  • 如果父类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表

请添加图片描述

24.3 同名函数与同名成员变量
24.3.1 同名函数
class Student {
public:
   void eat() {
        cout << "student :: eat() " << endl;
    }
};
class Worker {
public :
   void eat() {
         cout << "Worker ::eat()" << endl;
    }
 };
 class Undergraduate : public Student, public Worker {
public :
   void eat() {
      cout << "Undergraduate :: eat () " << endl;
   }
};

Undergraduate ug;
ug.Student::eat();   // Student::eat()
ug,Worker::eat();   // Worker::eat()
ug.Undergraduate :: eat();   // Undergraduate :: eat()
ug.eat();  //Undergraduate::eat()
24.3.2 同名成员变量
class Student {
public :
    int m_age;
};
class Undergraduate : public Student, public Worker {
 public :
    int m_age;
 };
 Undergraduate ug;
ug.m_age = 10;
ug.Student:: m_age = 20;
ug.Worker::m_age = 30;

cout << ug.Undergraduate::m_age << endl; //  10;
cout << ug.Student::m_age << endl;  // 20
cout << ug.Worker::m_age << endl;   // 30

该函数内存示意图:
请添加图片描述

24.3 菱形继承
  • 菱形继承带来的问题:
    • 最底下的子类从基类继承的成员变量冗余、重复
    • 最底下的子类无法访问基类的成员,有二义性
class Person {
    int m_age;
};
class Student : public Person {
     int m_score;
 };
 class Worker : public Person {
      int m_salary;
 };
 class Undergraduate : public Student, public Worker {
     int m_grade;
};

对象关系图:
请添加图片描述
对象内存分布图:
请添加图片描述

24.4 虚继承
  • 虚继承可以解决菱形继承带来的问题;
  • Person 类被称为虚基类
class Person {
      int m_age = 1;
};
class Student : virtual public Person {
      int m_score = 2;
};
class Worker : virtual public Person {
      int m_salary = 3;
};
class Undergraduate : public Student, public Worker {
      int m_grade = 4;
};

该代码的关系示意图:

请添加图片描述
其中红色部分代表:虚表指针与本类起始的偏移量(一般是0);
绿色部分代表:虚基类第一个成员变量与本类起始的偏移量。

请添加图片描述

25. 静态成员(static)

  • 静态成员:被static修饰的成员变量 \ 函数;

    • 可以通过对象(对象.静态成员)、对象指针(对象指针- > 静态成员)、类访问(类名::静态成员);
  • 静态成员变量:

    • 存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存;
    • 对比全局变量,它可以设定访问权限(publicprotectedprivate),达到局部共享的目的;
    • 必须初始化,必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离(在实现.cpp中初始化)
  • 静态成员函数:

    • 内部不能使用this指针(this是拿来存储对象的地址值,this指针只能用在非静态成员函数内部)
    • 不能是虚函数(虚函数只能是非静态成员函数));
    • 内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数
    • 非静态成员函数内部可以访问静态成员变量\函数;
    • 构造函数、析构函数不能是静态;
    • 当声明和实现分离时,实现部分不能带static
class Person {
 public :
     static int m_age;
     int m_height;
     int m_weight; 
};
int Person::m_age = 0; //静态成员变量必须在外面初始化
class Student : public Person {  
 }

int main() {
     cout << &Person :: m_age << endl; 
     cout << &Student :: m_age << endl; //都是一个全局变量,所以地址值相同
     return 0;
}
class Person {
 public :
     static int m_age;
     int m_height;
     int m_weight; 
};
int Person::m_age = 0; //静态成员变量必须在外面初始化
class Student : public Person {  
public:
     static int m_age;
 }
static Student::m_age = 1;
int main() {
     cout << &Person :: m_age << endl; 
     cout << &Student :: m_age << endl; //两个全局变量不同,所以地址值不同
     return 0;
}

类的继承是对于非静态变量的。

25.1 static应用举例

统计car的数量:

 ```cpp
class  Car {
        int m_price;
        static int ms_count; //只有类可以访问,外面不能访问的静态成员变量
public:
         static int gerCount() { //可以通过类名去调用getCount函数
            return ms_count;
         }
          Car(int price = 0) : m_price(price) {
              ms_count ++;
          }
          ~Car() {
              ms_count--;
          }
};
int Car::ms_count = 0; //静态成员变量需要在外面初始化
25.2 static应用之单例模式

单例模式:设计模式的一种,保证某个类永远只创建一个对象。

实现步骤:

  1. 构造函数私有化;
  2. 定义一个私有的static成员变量指向唯一的那个单例对象;
  3. 提供一个公共的访问单例对象的接口。
class Rocket {
private:
     Rocket() {}
    ~Rocket() {}  //防止用户在生成Rocket中delete掉对象
    static Rocket *ms_rocket;
public:
    static Rocket *sharedRockert() {
        //这里要考虑多线程安全
          if (ms_rocket == NULL) { 
                 ms_rocket = new Rocket();
           }
           return  ms_rocket;
     }
     static void deleteRocket() {
          // 这里要考虑多线程安全
          if (ms_rocket != NULL) {
              delete ms_rocket;  //只是堆空间的对象被释放了
              ms_rocket = NULL;  //ms_rocket指针仍然还在栈中,不会清零
           }
      }
};

26. const

  • const成员:const修饰的成员变量、非静态成员函数
  • const成员变量:
    • 必须初始化(类内部初始化),可以在声明的时候直接初始化赋值;
    • staticconst成员变量还可以在初始化列表中初始化。
  • const成员函数(非静态)
    • const关键字写在参数列表后面,函数的声明和实现都必须带const:
      • 内部不能修改非static成员变量
      • 内部只能调用const成员函数、static成员函数
      • const成员函数可以调用const成员函数
    • const成员函数和非const成员函数构成重载;
      • const对象(指针)优先调用非const成员函数;
    • const对象(指针)只能调用const成员函数、static成员函数

实例:
请添加图片描述

27. 引用类型成员

  • 引用类型成员变量必须初始化(不考虑static情况);
    • 在声明的时候直接初始化;
    • 通过初始化列表初始化。
class Car {
    int age;
    int &m_price = age;
public:
    Car(int &price) : m_price(price) { }
};

28. 拷贝构造函数

  • 拷贝构造函数是构造函数的一种在已存在的对象,创建完新对象后为对象赋值时调用);
  • 当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化
  • 拷贝构造函数的格式是固定的,接收一个const引用作为参数。
    在这里插入图片描述
28.1 调用父类的拷贝构造函数

子类进行初始化时,如果没有明显地调用父类的拷贝构造函数,就会采用父类的构造函数进行初始化成员变量

class Person {
     int m_age;
 public :
     Person(int age) : m_age(age) {
     Person(const Person &person) :m_age(person.age) { }
 };
 class Student : public Person {
    int score;
public :
    Student(int age, int score) : person(age), m_score(score) { }
    Student(const Student &student) : Person(student), m_score(student.m_score) { } //调用父类的拷贝构造函数来间接初始化成员变量
};
28. 2 拷贝构造函数
Car car(100, "BWM 730Li");
Car car2 = car;
Car car3(car2);
Car car4;
Car4 = car3;
  • car2car3都是通过拷贝构造函数初始化的,carcar4是通过非拷贝构造函数初始化。
  • car4 = car3是一个赋值操作(默认是浅复制),并不会调用拷贝构造函数。
28. 3 浅拷贝、深拷贝
  • 编译器默认的提供的拷贝是浅拷贝(shallow copy):

    • 将一个对象中所有成员变量的值拷贝到另一个对象
    • 如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
    • 可能会导致堆空间多次free的问题
  • 如果需要实现深拷贝(deep copy),就需要自定义拷贝构造函数:

    • 将指针类型的成员变量指向的内存空间,拷贝到新的内存空间。
28.3.1 浅拷贝
class Car {
      int m_price;
      char *m_name;
public:
       Car(int price = 0, char *name = NULL) : m_price(price), m_name(name) { }
       void display() {
          cout << "price is" << m_price << ", name is " <<  m_name << endl;
        }
 };
 Car *g_car;
 void test() {
    char name2[] = {'b', 'm', 'w', '\0'};
     g_car = new Car(100, name2);
 } 
    
 int main() {
     //const char * name = "bmw";
     // char name2[] = {'b', 'm', 'w', '\0'};
    test(); //函数一结束,name2内存就被释放了,g_car成了野指针。
    g_car -> display();
     return 0;
}
  • 如果释放了栈空间中的name数组的话,堆空间中的指针仍指向该处内存区,成为了野指针,使得访问用户未使用的空间,不安全。

该程序的内存示意图:
在这里插入图片描述

#include <iostream>
using namespace std;

class Car {
	int m_price;
	char* m_name;
public :
	Car(int price = 0, const char* name = nullptr) : m_price(price) { //const 形参可以接收常量和变量
		if (name == nullptr) return;

		//申请新的堆空间
		m_name = new char[strlen(name) + 1] {};
		//拷贝字符串数据到新的堆空间
		strcpy(m_name, name);

	}
	~Car() {
		if (m_name == nullptr) return;
		delete[] m_name;
		m_name = nullptr;
	}
	void display() {
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {
	//char name[] = { 'b', 'm', 'w', '\0' };
	Car car1(100, "bmw");
	Car car2 = car1; //Car car2(car1);
	car2->display();
	return 0;
}

改程序内存示意图:
请添加图片描述

  • 指针类型的变量只会拷贝地址值,堆空间中的内容可能会被释放两次
28.3.2 深拷贝
#include <iostream>
using namespace std;

class Car {
	int m_price;
	char* m_name;
	void copyName(const char* name) {
	    if (name == nullptr) return;
		//申请新的堆空间
		m_name = new char[strlen(name) + 1] {};
		//拷贝字符串数据到新的堆空间
		strcpy(m_name, name);
     }
public :
	Car(int price = 0, const char* name = nullptr) : m_price(price) { //const 形参可以接收常量和变量
		copyName(name);
	}
	Car(const Car &car) {    //自定义拷贝构造函数
	    this->m_price = car.m_price;
	    copyName(car.name);
	}
	~Car() {
		if (this -> m_name != nullptr) {
		   delete[] this->m_name;
	}
	void display() {
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {
	//char name[] = { 'b', 'm', 'w', '\0' };
	Car car1(100, "bmw");
	Car car2 = car1; //Car car2(car1)
	car1->display();
	return 0;
}
  • 将指针指向的内容拷贝到新的存储空间

该程序的内存示意图:
请添加图片描述

29. 对象型参数和返回值

  • 使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象(所以尽量使用对象的引用)。
class Car {
       int m_price;
public: 
       Car() { }
       Car(int  price) :m_price(price) {Car(const Car &car) :m_price(car.m_price)  { } 
 };
 void test1(Car car) { //实参结合时会调用拷贝构造函数
 }
 Car test2() {
      Car car(20); //Car(int price)
      return car; //会调用拷贝构造函数
 }

Car car1(10); //Car(int price)
test1(car1); //Car(const Car &car)

Car car2 = test2();  //Car(const Car &car)

Car car3(30); ///Car(int price)
car3 = test2(); //Car(const Car &car)

30. 匿名对象(临时对象)

  • 匿名对象:没有变量名、没有被指针指向的对象,用完后马上调用析构。
void test1(Car car) {

}
Car test2() {
    return Car(60);
}

Car(10); //Car(int price)
Car(20).display(); // Car(int price)

Car car1 = Car(30); // Car(int price)

test1(Car(40)); // Car(int price)

Car car3(50); // Car(int price)
car3 = test2(); // Car(int price)

31. 隐式构造(转换构造)

  • C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数。
void test1(Car car) {

}
Car test2() {
    return 70;
}

Car car1 = 10; // Car(int price)

Car car2(20); // Car(int price)
car2 = 30; // Car(int price),匿名对象

test1(40); // Car(int price)

Car car3 = test2(); // Car(int price)
  • 也可以使用关键字explicit禁止掉隐式构造(必须显式地去调用构造函数)。
class Car {
     int m_price;
pubic:
      Car() { }
      explicit Car(int price) :m_price(price) { }
      Car(const Car &car) :m_price(car.m_price) {};

32. 编译器自动生成的构造函数

  • C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数,比如:
    1. 成员变量在声明的同时进行了初始化;
    2. 有定义虚函数(编译器必须为对象建立虚表,对象多生成的一个字节);
    3. 虚继承了其他类(创建出来的对象最前面字节为虚表地址);
    4. 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义);

即:

class Car {
public:
      int m_price;
      Car() { } //Car类中自定义了构造函数
};
class Person {
public:
      Car car; // 生成调用Car中构造函数生成car对象
 };
 int main() {
      Person person; //生成person对象
      return 0;
 }

注:但是当Car类中无构造函数使,就不会调用构造函数,因为没有给person对象指定一些额外操作。

  1. 父类有构造函数(编译器生成或自定义)。
  • 总结:
    • 对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会为其自动生成无参的构造函数

33. 友元

  • 友元包括友元函数和友元类;
  • 如果是将函数A(非成员函数)声明为类C的友元函数,那么函数A就能直接访问类C对象的所有成员;
  • 如果将类A声明为类C的友元类,那么类A的所有成员都能直接访问类C对象的所有成员;
  • 友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能

例:

class Point {
       friend Point add(const Point &, const Point &);
       friend class Math;
 private:
       int m_x;
       int m_y;
 public:
       Point() { }
       Point(int x, int y) :m_x(x), m_y(y)  { }
};

Ponit add(const Point &p1, const Point &p2) {
     return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}

class Math {
    void test() {
       Point point;
       point.m_x = 10;
       point.m_y = 20;
     }
     static void test2() {
       Point point;
       point.m_x = 10;
       point.m_y = 20;
      }
};

34. 内部类

  • 如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)
  • 内部类的特点:
    1. 支持publicprotectedprivate权限;
    2. 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
    3. 成员函数可以直接不带类名、对象名访问其外部类的static成员;
    4. 不会影响外部类的内存布局(即创建外部类时,类的大小就是类的成员变量的大小)
    5. 可以在外部类内部声明,在外部类外面进行定义。
class Point {
      static void test1() {
           cout << "Point :: test1()" << endl;
       }
       static int ms_test2;
       int m_x;
       int m_y;
public:
      class Math {
      public:
            void test3() {
                   cout << "Point :: Math :: test3()" << endl;
                   test1();
                   ms_test2 = 10;
                  Point point;
                  point.m_x = 10;
                  point.m_y = 20;
            }
      };
};
34.1 内部类——声明和实现分离

方法一:

class Point {
     class Math {
          void test();
      };
};
void Point::Math::test() {

}

方法二:

class Point {
      class Math;
 };
 class Point::Math {
       void test() {
       }
 };

方法三:

class Point {
     class Math;
};
class Point :: Math {
    void test();
}
void Point :: Math :: test() {
}

35. 局部类

  • 在一个函数内部定义的类,称为局部类;

  • 局部类的特点:

    1. 作用域仅限于所在的函数内部;
    2. 其所有的成员必须定义在类内部,不允许定义static成员变量(当定义static型成员变量在类内部时,意味着使用前必须初始化,可以直接在类外(函数外)为该成员赋值,与第一条矛盾);
    3. 成员函数不能直接访问函数的局部变量(函数的局部变量只有在函数被调用的时候存储空间才存在)(static变量除外)

    在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Balaaam

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值