C++ 基础语法,类,this指针,引用,内存布局,构造函数

cin cout

  • C++ 中常使用 cin 、 cout 进行控制台的输入、输出
  • cin 用的右移运算符 cout 用的是左移运算符
  • endl 是换行的意思
  int main() {
    int age;
    cin >> age;
    cout << "age is" << age << endl;
  }

函数重载

  • 规则
    • 函数名相同
    • 参数个数不同,参数类型不同,参数顺序不同
  • 注意
    • 返回值类型与函数重载无关
    • 调用函数时,实参隐式类型转换可能会产生二义性
  • 本质
    • 采用了 name mangling 或者叫 name decoration 技术
    • C++ 编译器默认会对符号名(变量名、函数名等)进行改编、修饰,有些地方翻译为“命名倾轧”
    • 重载时会生成多个不同的函数名,不同编译器( MSVC 、 g++g++)有不同的生成规则
    • 通过 IDA 打开 【 VS_Release_ 禁止优化 】 可以看到

extern “C”

  • extern "C” 修饰的代码会按照 C 语言的方式去编译
extern "C" {
  void func() {
    cout << "func()" << endl;
  }

  void func(int a) {
    cout << "func(int a) " << a << endl;
  }
}
  • 如果函数同时有声明和实现, 要让函数声明被 " extern " 修饰 ,函数实现可以不修饰

    extern "C" void func();
    extern "C" void func(int a);
    
    extern "C" {
      void func() {
    
      }
    
      void func(int a) {
    
      }
    }
    
    
  • 由于 C 、 C++ 编译规则的不同,在 C 、 C++ 混合开发时,可能会经常出现以下操作, C++ 在调用 C 语言 API 时,需要使用 extern " 修饰 C 语言的函数声明

extern-c.png
  • 有时也会在编写 C 语言代码中直接使用 extern “C” ,这样就可以直接被 C++ 调用

    extern-c1.png

volidate 关键字

  • 不论何时都从内存中读取数据,不使用编译器的优化后的值

默认参数

  • C++ 允许函数设置默认参数,在调用时可以根据情况省略实参。规则如下:
    • 默认参数只能按照右到左的顺序
    • 如果函数同时有声明、实现,默认参数只能放在函数声明中
    • 默认参数的值可以是常量、全局符号(全局变量、函数名)
  • 函数重载、默认参数可能会产生冲突、二义性(建议优先选择使用默认参数)

内联函数(inline function)

  • 使用 inline 修饰函数的声明或者实现,可以使其变成内联函数
    • 建议声明和实现都增加 inline 修饰
  • 特点
    • 编译器会将函数调用直接展开为函数体代码
    • 可以减少函数调用的开销
    • 会增大代码体积
  • 注意
  • 尽量不要内联超过 10 行代码的函数
  • 有些函数即使声明为 inline ,也不一定会被编译器内联,比如递归函数

内联函数与宏

  • 内联函数和宏,都可以减少函数调用的开销
  • 对比宏,内联函数多了语法检测和函数特性
  • 思考以下代码的区别
#define sum (x) (x +
inline int sum( int x ) { return x + x ;
int a = 10; sum(a++);

引用(Reference)

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

const

  • const 是常量的意思,被其修饰的变量不可修改
    • 如果修饰的是类、结构体(的指针),其成员也不可以更改
    • 以下 5 个指针分别是什么含义?
      int age = 10;
      const int *p0 = &age;
      int const *p1 = &age;
      int * const p2 = &age;
      const int * const p3 = &age;
      int const * const p4 = &age;
    
    • 上面的指针问题可以用以下结论来解决:
      • const 修饰的是其右边的内容
     int age = 10;
     int score = 20;
     /*
       *p0 = 20; // 失败, 不能改变指向变量的值
       p0 = &score; // 成功,可以改变指针的指向
      */
     const int *p0 = &age;
     /*
      *p1 = 20; // 失败, 不能改变指向变量的值
      p1 = &score; // 成功,可以改变指针的指向
      */
     int const *p1 = &age;
     /*
      *p2 = 20; // 成功, 可以改变改变指向变量的值
      p2 = &score; // 失败,不能改变指针的指向
      */
     int * const p2 = &age;
     /**
      *p3 = 20; // 失败, 不能改变改变指向变量的值
      p3 = &score; // 失败,不能改变指针的指向
      */
     const int * const p3 = &age;
     /**
      *p4 = 20; // 失败, 不能改变改变指向变量的值
       p4 = &score; // 失败,不能改变指针的指向
      */
     int const * const p4 = &age;
    
image.png

常引用(Const Reference)

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

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

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

数组的引用

  • 常见的 2 种写法
    int array[] = {10, 20, 30};
    int (&ref)[3] = array;
    int * const &ref2 = array;
    

引用的本质

  • 就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
  • 一个引用占用一个指针的大小

  • C++ 中可以使用 struct 、 class 来定义一个类

    • struct 和 class 的区别
      • struct 的默认成员权限是 public
      • class 的默认成员权限是 private
    // 类的定义
    struct Person {
      // 成员变量
      int m_age;
    
      // 成员函数
      void run() {
        cout << m_age <<"run()" <m_age = 30;
    p->run();
    
  • 上面代码中 person 对象、 pPerson 指针的内存都是在函数的栈空间,自动分配和回收的, person对象地地址就是成员变量m_age的地址

  • 可以尝试反汇编 struct 和 class ,看看是否有其他区别

  • 实际开发中,用 class 表示类比较多

对象的内存布局

  • 成员变量的类型相同时,对象的内存为所有成员变量的内存之和
  • 类型不同时,采用内存对齐方式计算
  • 空对象占用的内存空间为:1:c++编译器会给每个空对象分配一个字节空间,是为了区分空对象占用内存的位置
  • 每个空对象也应该有一个独一无二的内存地址
// 类的定义
struct Person {
  // 成员变量
  int m_id;
  int m_age;
  int m_height;

  // 成员函数
  void display() {
    cout << m_id << endl;
    cout << m_age << endl;
    cout << m_height << endl;
  }
}

Person person;
person.m_id = 10;
person.m_age = 20;
person.m_height = 30;

// 创建指针
Person *pPerson = (Person*)&person.m_age; // 指针指向person对象对象的第一个地址
pPerson->m_id = 40; // 相当于是person对象的第一个地址赋值,给m_age赋值
pPerson->m_age = 50;

person.display(); //输出 10 40 50

this

  • this 是指向当前对象的指针
  • 对象在调用成员函数的时候,会自动传入当前对象的内存地址
  • 指向被调用的成员函数/变量所属的对象
  • 当形参和成员变量同名时i,可用this指针区分
  • 在类的非静态成员函数中返回对象本身,可使用 return *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 "  <<  this->m_age << endl;
      cout << "m_height is "  <<  this->m_height << endl;
  }
};

class Perosn {
 public:
     static int m_age;
     
     Perosn& addAge(int age) {
         this->m_age = age;
         return  *this;
     }
 }
 
 void testPerson() {
     Perosn p;
     p.addAge(10).addAge(10).addAge(10);
 }

封装

  • 成员变量私有化,提供公共的 getter 和 setter 给外界去访问成员变量
struct Person {
private:
    // 成员变量
    int m_id;
public:
    int m_age;
    int m_height;

    // 成员函数
    void display() {
        cout << "m_id is " << this->m_id << endl;
        cout << "m_age is "  <<  this->m_age << endl;
        cout << "m_height is "  <<  this->m_height << endl;
    }

    void setId(int id) {
        this->m_id = id;
    }

    int getId() {
       return this->m_id;
    }
};

内存空间的布局

memory_zone.png
  • 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域
    • 代码段(代码区)
      • 用于存放代码
    • 数据段(全局区)
      • 用于存放全局变量等
      • 静态变量
      • 存放常量(符串常量,const修饰的全局变量)
  • 栈空间
    • 存放局部变量,函数参数
    • 栈区的数据有编译器管理开辟和释放
    • 注意:不要返回局部变量的地址
    • 每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间
    • 自动分配和回收
  • 堆空间
    • 需要主动去申请和释放
    • 由程序员分配释放,当程序结束时,由操作系统回收
    • 在C++中主要利用new在堆去开辟内存, 利用delete释放

堆空间

  • 在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
  • 堆空间的申请 释放
    • malloc \ free
    • new \ delete
    • new [] delete[]
  • 注意

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

    • 利:提高开发效率,避免内存使用不当或泄露
    • 弊:不利于开发人员了解本质,永远停留在 API 调用和表层语法糖,对性能优化无从下手

堆空间初始化

int *p0 = (int*)malloc(sizeof(int)); //p1 未初始化
memset(p0, 0, sizeof(int*)); // 将p1的每一个字节都初始化为0
int *p1 = new int; //未初始化
int *p2 = new int(); //被初始化为0
int *p3 = new int(3); // 被初始化为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}; // 数组首元素被初始化为6,其他元素初始化为0

对象的内存

  • 对象的内存可以存在于 3 种地方

    • 全局区(数据段):全局变量
    • 栈空间:函数里面的局部变量
    • 堆空间:动态申请内存( malloc 、 new 等)
    // 全局区
      Person person;
      int test() {
        // 栈空间
        Person person;
        // 堆空间
        Person * p = new Person;
        return 0;
      }
    

构造函数(Constructor)

  • 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
  • 特点
    • 函数名与类同名,无返回值( void 都不能写),可以有参数,可以重载,可以有多个构造函数
    • 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象
  • 注意
    • 通过 malloc 分配的对象不会调用构造函数
  • 一个广为流传的、很多教程 书籍都推崇的错误结论
    • 默认情况下,编译器会为每一个类生成空的无参的构造函数
    • 正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数

默认情况下,成员变量的初始化

  struct Person {
         int m_age;

         Person() {
             /// 将所有成员变量清0
             memset(this, 0, sizeof(Person));
             cout << "Person()" << endl;
         }

         Person(int age) {
             m_age = age;
             cout << "Person(age)" << endl;
         }
     };

   // 全局区:成员变量初始化为0
   Person g_person0; // Person()
   Person g_person1(); // 不会调动构造方法,仅仅是函数的声明
   Person g_person2(10); // Person(int)

   void test() {
      // 栈空间:没有初始化成员变量
      Person person0;// Person()
      Person person1(); // 不会调动构造方法,仅仅是函数的声明
      Person person2(30); // Person(int)

      // 堆空间:没有初始化成员变量
      Person * p0 = new Person;// Person()
      Person * p1 = new Person();// Person()
      Person * p2 = new Person(30); // Person(int)

      Person person1; //栈空间(成员变量不会不被初始化)

      // 堆空间
      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
   }
  • 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化

构造函数的分类及调用

  • 按参数分类:有参构造和无参构造
  • 按类型分为: 普通构造和拷贝构造

三种调用方式

  • 括号法
    Person p = p(10);
    
  • 显示法
    Person p = Person(10);
    
  • 隐式转换法
    Person p = 10; // 相当于写了 Person p = Person(10)
    

构造函数调用规则

  • 默认情况下(有需要处理事的时候),c++编译器至少给一个类添加3个函数
    • 默认构造函数
    • 默认析构函数
    • 默认拷贝函数
  • 如果用户定义有参构造函数,c++不再提供默认无参构造函数,但是会提供默认的拷贝构造
  • 如果用户定义拷贝构造函数, c++不会再提供其他构造函数
  • 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反

析构函数(Destructor)

  • 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
  • 特点
    • 函数名以 开头,与类同名,无返回值( void 都不能写),无参,不可以重载,有且只有一个析构函数
  • 注意
    • 通过 malloc 分配的对象 free 的时候不会调用构造函数
    • 构造函数、析构函数要声明为 public ,才能被外界正常使用
obj_mng.png

你可能感兴趣的:(C++ 基础语法,类,this指针,引用,内存布局,构造函数)