cpp_02_函数重载_动态内存分配_左值右值_引用_内联函数

2023-12-17 16:32:43

1? 函数重载

1.1? 定义

????????要求:

? ? ? ? 1)同一作用域内

? ? ? ? 2)函数名相同

? ? ? ? 3)形参表不同(与形参个数及每个形参类型有关,与形参名无关)

? ? ? ? 重载关系的函数调用哪个:

? ? ? ? 根据实参类型和形参类型进行匹配,调用最匹配的函数

// overload_pre.cpp
// 函数之间的关系--重载关系(1.同一作用域内  2.函数名相同  3.形参表不同)
// 形参表是否相同 与 形参名无关 与 形参的个数 以及 每一个对应形参的类型有关
#include <iostream>
using namespace std;

void foo( char* c, short s ) {
    cout << "1. foo" << endl;
}
void foo( int i, double d ) {
    cout << "2. foo" << endl;
}
void foo( const char* c, short s ) {
    cout << "3. foo" << endl;
}
void foo( double d, int i ) {
    cout << "4. foo" << endl;
}

// int foo( double i, int d ) {} // error-是否为重载关系和返回值类型无关
// void foo( double i, int d ) {} // error-形参表是否相同 与 形参名无关
int main( void ) {
    char* c;    short s;
    foo( c, s ); // 1
    const char* cc;
    foo( cc, s ); // 3
    int i;  double d;
    foo( i, d ); // 2
    foo( d, i ); // 4
    return 0;
}

1.2? 重载和隐藏:

? ? ? ? -只有同一作用域内的同名函数才涉及重载的关系

? ? ? ? -不同作用域的同名函数涉及的是隐藏关系(定义表隐藏可见表)

// overload.cpp 详谈同一作用域
#include <iostream>
using namespace std;
namespace ns1 {
    void foo( char* c, short s ) {
        cout << "1. foo" << endl;
    }
    void foo( int i, double d ) {
        cout << "2. foo" << endl;
    }
}
namespace ns2 {
    void foo( const char* c, short s ) {
        cout << "3. foo" << endl;
    }
    void foo( double d, int i ) {
        cout << "4. foo" << endl;
    }
}
int main( void ) {
    using ns2::foo; //名字空间声明,从这行代码开始ns2中foo引入当前作用域(出现在定义表中)
    using namespace ns1;//名字空间指令,从这行代码开始ns1中的foo在当前作用域可见(出现在可见表中)
    char* c;    short s;   
    foo( c, s ); // 第3个foo将第1个foo函数隐藏
    return 0;
}

1.3? 重载匹配优先级

????????1)普通方式调用重载关系的函数:

? ? ? ? 完全匹配?>?常量转换?>?升级转换(小转大)?>?标准转换(大转小)?>?自定义转换?>?省略号匹配

? ? ? ? 2)函数指针方式调用重载关系的函数:

????????函数指针本身的类型决定其调用哪个版本的重载函数。

????????工作中建议完全匹配。

// overload2.cpp 重载匹配优先级
#include <iostream>
using namespace std;

void foo( char* c, short s ) { // _Z3fooPcs 完全匹配
    cout << "1. foo(char*, short)" << endl;
}
void foo( const char* c, short s ) { // _Z3fooPKcs 常量转换
    cout << "2. foo(const char*, short)" << endl;
}
void foo( char* c, int s ) { // _Z3fooPci 升级转换(小转大,没有数据损失)
    cout << "3. foo(char*,int)" << endl;
}
void foo( char* c, char s ) { // _Z3fooPcc 标准转换(大转小,可能数据损失)
    cout << "4. foo(char*,char)" << endl;
}
void foo( ... ) { // _Z3fooz 省略号(可变长)匹配
    cout << "5. foo(...)" << endl;
}
int main( void ) {
    char * c;   short s;
    foo( c,s ); //_Z3fooPcs(c,s) 
    // 普通方式调用重载关系的函数,根据实参类型和形参类型匹配,来确定调用哪个foo

    void(*pfunc)(const char*,short) = foo; // _Z3fooPKcs  定义函数指针,形参可无名
    pfunc(c,s); // 函数指针方式调用重载关系的函数,
                // 根据函数指针本身的类型,来确定调用哪个foo
    return 0;
}

? ? ? ? 注意上述代码,定义函数指针,可以不写形参名。?

1.4? 重载揭秘

? ? ? ? 重载是通过C++换名机制来实现的:

? ? ? ? nm a.out 命令 查看函数符号名

????????

? ? ? ? 通过extern? "C" 可以要求C++编译器按照C方式编译函数,即不做换名,当然也就无法重载

// extern/cal.cpp
extern "C" {                      //extern "C" {} ,缩不缩进都可
    int add( int a, int b ) {
        return a + b;
    }

    int sub( int a, int b ) {
        return a - b;
    }
}
// extern/main.c
#include <stdio.h>

int main( void ) {
    int c = add( 5, 3 );
    int d = sub( 5, 3 );
    printf("c=%d,d=%d\n", c, d);
    return 0;
}
//g++ -c cal.cpp  
//nm cal.o  //由于extern "C",函数没换名,与c代码中一致
//gcc -c main.c
//nm main.o
//gcc main.o cal.o  
//./a.out

2? 动态内存(堆内存)分配

? ? ? ? 可以继续使用标准C库函数malloc()/free(),

????????free(野指针)后果很严重(段错误,double free),free(空指针)安全:

// new.cpp 动态(堆)内存分配
#include <iostream>
#include <cstdlib>
using namespace std;

int main( void ) {
    int* pm = (int*)malloc( 4 );
    cout << "*pm=" << *pm << endl; // 初始值为0
    free( pm ); // 当这行代码执行结束后,pm指向的堆内存被释放,进而pm变为野指针
    pm = NULL;  // pm变为空指针
    free( pm ); // 给free传递的为野指针,释放野指针后果很严重,释放空指针是安全
    return 0;
}

????????更建议使用new/delete操作符在堆中分配/释放内存:

? ? ? ? ? ? ? ? int*? pi? =? new? int;? ? ? ? ?//初始值一般为0

? ? ? ? ? ? ? ? delete? pi;

????????在分配内存的同时初始化

? ? ? ? ? ? ? ? int*? pi? =? new? int( 100 );? ?//初始值为100

????????可以数组方式new:

? ? ? ? ? ? ? ? int*? pi? =? new? int [4] {10, 20, 30, 40};? // {}方式是11标准才支持的,编译时-std=c++11

????????????????想申请16字节,实际多申请4字节,存储数组元素的个数

????????但也要数组方式delete:

? ? ? ? ? ? ? ? delete[]? pi;? ? ? ? ? ? ? ? ? ? ? ? ? ? 加[],才能将多申请的4字节也释放掉

? ? ? ? 通过new操作符分配N维数组,返回N-1维数组指针

? ? ? ? ? ? ? ? int (*pa) [4] = new int [3][4];? // 返回值类型是 int (*)[4]

? ? ? ? ? ? ? ? int (*pb) [4][5] = new int [3][4][5];? // 返回值类型是 int (*)[4][5]

? ? ? ? 不能通过delete操作符释放已释放过的内存。

? ? ? ? delete野指针后果很严重(段错误,double free),delete空指针安全。

? ? ? ? 故建议释放指针指向的内存后,立即置空:? ?

????????????????delete(pn);? ? ?

????????????????pn = NULL;

? ? ? ? new操作符申请内存失败,将抛出异常?。

// new.cpp 动态(堆)内存分配
#include <iostream>
#include <cstdlib>
using namespace std;

int main( void ) {
    int* pm = (int*)malloc( 4 );
    cout << "*pm=" << *pm << endl; // 初始值为0
    free( pm ); // 当这行代码执行结束后,pm指向的堆内存被释放,进而pm变为野指针
    pm = NULL;
    free( pm ); // 给free传递的为野指针,释放野指针后果很严重,释放空指针是安全

    int* pn = new int(100);
    cout << "*pn=" << *pn << endl; // 可以自己指定初始值为100
    delete pn; // 当这行代码执行结束后, pn指向的堆内存被释放,进而pn变为野指针
    pn = NULL;
    delete pn; // 给delete传递野指针,释放野指针后果很严重,释放空指针是安全

    int* parr = new int[4]{10,20,30,40};//以数组方式new一块内存,永远返回第1个元素的地址
    for( int i=0; i<4; i++ ) {
        cout << parr[i] << ' ';
    }
    cout << endl;
    delete[] parr; // 数组方式new的也要以数组方式delete

    // 不管是几维数组,都应该当做一维数组看待
    int(*p)[4] = new int[3][4]; // 返回值是一维数组类型的指针
    delete[] p;

    try {
        new int[0xFFFFFFFF];
    }
    catch(...) {  //捕获...
    
    }
    return 0;
}
//g++ new.cpp -std=c++11

3? 左值和右值

? ? ? ? C++所有数据,不是左值,就是右值:

????????左值:能够取地址的值,通常具名

? ? ? ? 右值:不能取地址的值,通常匿名

// lrvalue.cpp 左值 和 右值
#include <iostream>
using namespace std;

int foo( ) {
    int m=888;
    return m;
}

int main( void ) {
// 当前作用域的生命期
// 具名内存-->能够取址-->左值|非常左值(无const修饰)
//                           |常左值  (有const修饰)
    int a = 10;
    &a;
    a = 15;

    const int b = 10;
    &b;
//  b = 15; // error

// 语句级生命期
// 匿名内存-->不能取址-->右值|直接更改右值毫无意义(98/03标准给出结论)
//
    10;
//  &10; // error
//  10 = 15; // error

    /*|888|*/foo( ); // (1)分配一块内存空间  (2)生成跳转指令
//  &foo( ); // error
//  foo( ) = 15; // error
    return 0;
}

4? 引用(如影随形,从一而终)

? ? ? ? 1)引用即内存的别名

? ? ? ? ? ? ? ? int? a = 10;? // a是内存的真名

? ? ? ? ? ? ? ? int&? b = a;? // 不是赋值,而是给a起别名!(给引用b,赋真名)

? ? ? ? 2)C++层面,引用本身不占内存并非实体

? ? ? ? ? ? ? 对引用(别名)的所有操作都是在对目标内存进行操作。

? ? ? ? 3)引用必须初始化,且不能更换目标

? ? ? ? ? ? ? ? int? c = 20;

? ? ? ? ? ? ? ? b = c;? // 仅仅是对引用的目标内存a进行赋值

? ? ? ? 4)不存在引用的引用

? ? ? ? ? ? ? ? int? a = 10;

? ? ? ? ? ? ? ? int&? b = a;? ?//别名b,真名a

? ? ? ? ? ? ? ? int&? d = b;? ?//别名d,真名a

? ? ? ? 5)引用的常属性必须和目标的常属性“一致”?

? ? ? ? ? ? ? ? const? int? e = 10;

? ? ? ? ? ? ? ? const? int&? f = e;? ?// OK

? ? ? ? ? ? ? ? int&? g = e;? ?// ERROR

? ? ? ? 6)可以限定更加严格

? ? ? ? ? ? ? ? int? a = 10;

? ? ? ? ? ? ? ? const int& h = a; // OK

// alias.cpp 引用:就是一块内存的别名
#include <iostream>
using namespace std;

int main( void ) {
    int a = 10;
    int& b = a; // 这并不是利用a的数据给b赋值,而应该理解为 引用b是a所代表内存的别名

    b = 20; // 对 引用b赋值,其实就是在对引用b的目标内存(a)赋值
    cout << "a=" << a << ", b=" << b << endl;//读取引用b的值,
                                             //其实读取的为引用b的目标内存(a)的值
    cout << "&a:" << &a << ", &b:" << &b << endl;
    // 取引用b的地址,其实取的为引用b的目标内存(a)的地址
    
    int c = 30;
    b = c;
    cout << "a=" << a << ", b=" << b << ", c=" << c << endl;
    cout << "&a:" << &a << ", &b:" << &b << ", &c:" << &c << endl;

    return 0;
}

? ? ? ? 7)引用可以延长右值的生命周期?

? ? ? ? 8)常引用? 即? 万能引用?

// alias2.cpp 

5? 内联函数

? ? ? ? 调用普通函数(非内联函数)的问题:

? ? ? ? -每个普通函数调用语句都需要发生跳转操作,这种跳转操作会带来时间开销

????????????????

? ? ? ? 内联就是用函数已被编译好的二进制代码,替换对该函数的调用指令。

? ? ? ? 内联在保证函数特性的同时,避免了函数调用的时间开销

????????????????

// inline.cpp 内联函数:编译器的优化策略 
#include <iostream>
using namespace std;

void foo( int x ) { // 非内联(普通)函数
    cout << "foo(int): " << x << endl;
}

inline void bar( int x ) { // 内联函数
    cout << "bar(int): " << x << endl;
}

int main( void ) {
    
    foo( 10 ); // 将此处替换为 跳转指令
    foo( 20 ); // ...
    foo( 30 ); // ...
    bar( 10 ); // 见此处替换为 bar函数编译后产生的二进制指令集
    bar( 20 ); // ... 
    bar( 30 ); // ...
    return 0;
}

? ? ? ? 内联会使文件的体积变大,进而导致进程的内存变大,因此只有频繁调用简单函数才适合内联。

? ? ? ? 稀少被调用的复杂函数递归函数都不适合内联。

? ? ? ? inline关键字仅表示期望该函数被优化为内联,但是否适合内联则完全由编译器决定。

文章来源:https://blog.csdn.net/qq_33331020/article/details/135036687
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。