Skip to content

Latest commit

 

History

History
275 lines (235 loc) · 9.79 KB

l_09.md

File metadata and controls

275 lines (235 loc) · 9.79 KB

Lesson9 函数&内存



1. 函数的默认参数

如果有传入数据,就用传入数据,否则用默认值
语法:返回值类型 函数名 (形参 = 默认值){}

int func(int a, int b = 20, int c = 30) {
  return a + b + c;
}
int main() {
  std::cout << func(10, 30) << std::endl; // output: 70
}

如果参数表某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
如果函数声明有默认参数,函数的实现就不能有默认参数,声明和实现只能有一个默认参数

int func(int a = 10, int b = 10);
int func(int a, int b) {
  return a + b;
}
int main() {
  std::cout << func(10, 10) << std::endl;  // 20
}

2. 函数的占位参数

语法:返回值类型 函数名(数据类型) {}
只有参数类型声明,没有变量名
若无默认值,调用函数时必须填补该位置,占位参数在函数体内无法调用

void func(int n, int) {
  std::cout << "this is a func" << std::endl;
}
int main() {
  func(10, 10);
}

函数占位参数也可以有默认值

void func(int n, int = 10) {
  std::cout << "this is a func" << std::endl;
}
int main() {
  func(10);
}

3. 函数重载(静态多态 Static Polymorphism

函数重载作用:函数名可以相同 提高复用性
函数重载条件:同一个作用域下 函数名称相同 函数参数类型不同或个数不同或顺序不同

  • 重载函数的调用和参数有关

    void func() {
      std::cout << "func" << std::endl;
    }
    
    void func(int a) {
      std::cout << "func(int a)" << std::endl;
    }
    
    void func(double a) {
      std::cout << "func(double a)" << std::endl;
    }
    
    void func(int a, double b) {
      std::cout << "func(int a,double b)" << std::endl;
    }
    
    void func(double a, int b) {
      std::cout << "func(double a,int b)" << std::endl;
    }
  • 引用作为重载条件

    void func(int& a) {
      std::cout << "func2(int &a)" << std::endl;
    }
    
    void func(const int& a) {
      std::cout << "func2(const int &a)" << std::endl;
    }
  • 函数重载碰到默认参数

    void func3(int a, int b = 10) {
      std::cout << "func3(int a,int b = 10)" << std::endl;
    }
    
    void func3(int a) {
      std::cout << "func3(int a)" << std::endl;
    }
    
    int main() {
      func3(10); // error
      func3(10, 20); // legal
    }
  • 函数返回值不可以作为重载条件

    以下操作不合法

    void func(double a, int b){
      std::cout << "void func(double a,int b)" << std::endl;
    }
    int func(double a, int b) { // error
      std::cout << "int func(double a,int b)" << std::endl;
    }

4. new操作符

  • new的作用

    自由存储区(free_store)(包括堆区和静态存储区)创建数据 ;new返回的是该数据类型的指针;如果想释放自由存储区的数据,利用关键字delete释放指针

  • 在堆区利用new创建数据

    int* func() {
      int* p = new int(10); // 在堆区创建整型数据10
      return p;
    }
    
    int main() {
      int* p = func();
      cout << *p << endl;
      delete p;
      std::cout << *p << std::endl; // illegal
    }.
  • 在堆区利用new开辟数组

    int* arr = new int[10]; // 在堆区创建长度为10的整型数组
    
    for (int i = 0; i < 10; i++) {
      arr[i] = i + 100;  // 100-109
    }
    for (int i = 0; i < 10; i++) {
      std::cout << arr[i] << std::endl;
    }
    
    // 释放堆区数组
    delete[] arr;  // 加[]释放数组
  • 分配异常

    new内存分配失败时,会抛出bac_alloc异常,它不会返回NULLmalloc分配内存失败时返回NULL。 在使用C语言时,我们习惯在malloc分配内存后判断分配是否成功:

    int *a  = (int *)malloc ( sizeof (int ));
    if(NULL == a) {
      ...
    } else {
      ...
    }

    从C语言走入C++阵营的新手可能会把这个习惯带入C++:

    int * a = new int();
    if(NULL == a) {
      ...
    } else {   
      ...
    }

    实际上这样做一点意义也没有,因为new内存分配失败时,会抛出bad_alloc异常,它不会返回NULL,分配失败时如果不捕捉异常,那么程序就会异常退出,我们可以通过异常捕捉的方式获取该异常。 正确的做法应该是使用异常机制:

    try {
      int *a = new int();
    } catch (bad_alloc) {
      ...
    }
  • 空指针nullptr

    C中,NULL被宏定义为(void*)0

    #define NULL ((void*)0)

    C++中,void*的类型转换被取消(为了支持函数重载),NULL被宏定义为整型0,而非空指针。为了避免歧义,C++11新增nullptr关键字(而非宏定义)专门表示空指针,避免歧义,但是NULL在新版中也可用

    #define NULL 0

  • 补充:newmalloc 的区别 摘自知乎

    1. 属性的区别 new/delete:这两个是C++中的关键字,若要使用,需要编译器支持;

      malloc/free:这两个是库函数,若要使用则需要引入相应的头文件才可以正常使用。

    1. 使用上的区别 malloc:申请空间需要显式填入申请内存的大小;

      new:无需显式填入申请的内存大小,new会根据new的类型分配内存。

    实例:

    /** malloc/free用例 **/
    int*ma = (int*)malloc(4);
    free(ma);
    /** new/delete用例 **/
    int* ne = new int(0);
    1. 内存位置的区别
      new:此操作符分配的内存空间是在自由存储区;

      malloc:申请的内存是在堆空间。

      C/C++的内存通常分为:堆、栈、自由存储区、全局/静态存储区、常量存储> 区。可能除了自由存储区,其他的内存分布大家应该都比较熟悉。

      是C语言和操作系统的术语,堆是操作系统所维护的一块特殊内存,它提 供了动态分配的功能,当运行程序调用malloc()时就会从中分配,调用 free>()归还内存。那什么是自由存储区呢?

      自由存储区 是C++中动态分配和释放对象的一个概念,通过new分配的内存区域可以称为自由存储区,通过delete释放归还内存。自由存储区可以是堆、全局/静态存储区等,具体是在哪个区,主要还是要看new的实现以及C++编译器默认new申请的内存是在哪里。但是基本上,很多C++编译器默认使用堆来实现自由存储,运算符new和delete内部默认是使用malloc和free的方式来被实现,说它在堆上也对,说它在自由存储区上也正确。因为在C++中new和delete符号是可以重载的,我们可以重新实现new的实现代码,可以让其分配的内存位置在静态存储区等。而malloc和free是C里的库函数,无法对其进行重载。

    1. 返回类型的区别 new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。所以在C++程序中使用new会比malloc安全可靠。
    1. 分配失败情况的区别 malloc分配内存失败时返回NULL,我们可以通过判断返回值可以得知是否分配成功;

      new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL,分配失败时如果不捕捉异常,那么程序就会异常退出,我们可以通过异常捕捉的方式获取该异常。

    1. 定义对象系统调度过程的区别 使用new操作符来分配对象内存时会经历三个步骤:

      调用operator new 函数(对于数组是operator new[])分配一块足够的内存空间(通常底层默认使用malloc实现,除非程序员重载new符号)以便存储特定类型的对象;

      编译器运行相应的构造函数以构造对象,并为其传入初值。

      对象构造完成后,返回一个指向该对象的指针。

      使用delete操作符来释放对象内存时会经历两个步骤:

      调用对象的析构函数。

      编译器调用operator delete(或operator delete[])函数释放内存空间(通常底层默认使用free实现,除非程序员重载delete符号)。

      自己可以通过实例去验证下,此处就不展开例程了。

    1. 扩张内存大小的区别 malloc:使用malloc分配内存后,发现内存不够用,那我们可以通过realloc函数来扩张内存大小,realloc会先判断当前申请的内存后面是否还有足够的内存空间进行扩张,如果有足够的空间,那么就会往后面继续申请空间,并返回原来的地址指针;否则realloc会在另外有足够大小的内存申请一块空间,并将当前内存空间里的内容拷贝到新的内存空间里,最后返回新的地址指针。

      new:new没有扩张内存的机制。

  • 拓展:C++智能指针 贴两个链接,有兴趣看看

    cppreference: std::unique_ptr
    cppreference: std::shared_ptr


edit Serein
audit NKID00