C++对C的扩展(下)

简介: C++对C的扩展

C++对C的扩展(上):https://developer.aliyun.com/article/1459382


引用作用于数组


  • 使用小括号结合
  • 配合typedef
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void test()
{
    int arr[5] = {1, 2, 3, 4, 5};
    int (&a)[5] = arr;
    cout << a[0]  << a[1] << endl;
    typedef int ARR[5];
    ARR aa = {1, 2, 3, 4, 5};
    ARR &b = aa;
    cout << b[0]  << b[1] << endl;
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}


引用作为函数参数


最常见的是引用作为函数参数和返回值中,当引用被作为函数参数时,在函数内部对引用的任何修改,将对函数外部的参数产生改变。当然可以通过传递一个指针来做和传递引用相同的事情,但是引用具有更清晰的语法。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void myswap1(int a, int b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
void myswap2(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
void myswap3(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
int main(int argc, char* argv[])
{
    int a = 10, b = 20;
    myswap1(a, b);
    cout << a << " " << b << endl;
    myswap2(&a, &b);
    cout << a << " " << b << endl;
    myswap3(a, b);
    cout << a << " " << b << endl;
    return 0;
}

通过引用参数产生的效果和传地址产生的效果是一样的,引用语法更简单:


函数调用的时候不需要加&符号。

在被调函数中不必在参数前面加*符 引用作为其他变量的别名而存在,因此在一些场合可以代替指针。C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。


引用作为函数返回值


如果从函数中返回一个引用,必须想从函数中返回一个指针一样对待。当函数返回引用时,引用关联的内存一定要存在。


例1:

函数的返回值是引用时候,不要返回局部变量

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person(string name):name(name){}
    string name;
};
Person &func1()
{
    Person num("100");
    return num;
}
int main(int argc, char* argv[])
{
    Person &ret = func1();
    cout << ret.name << endl;
    return 0;
}


例2:

当函数返回值为左值的时候,函数的返回值类型必须为引用。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person(string name):name(name){}
    string name;
};
Person &func1()
{
    static Person num("100");
    // 函数的返回值是引用时候,不要返回局部变量
    return num;
}
void test()
{
    func1().name = "200";
}
int main(int argc, char* argv[])
{
    Person &ret = func1();
    test();
    cout << ret.name << endl;
    return 0;
}


引用的本质(C++中无法看到)


内部实现更像是常量指针


指针的引用

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void func1(char **p)
{
    *p = (char *)calloc(1, 32);
    strcpy(*p, "hello world");
}
void func2(char *&p)
{
    p = (char *)calloc(1, 32);
    strcpy(p, "hello world");
}
void test()
{
    char *p = nullptr;
    func1(&p);
    cout << p << endl;
    free(p);
    char *p2 = nullptr;
    func2(p2);
    cout << p2 << endl;
    free(p2);
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}


常引用


常量引用主要是应用做函数参数,尤其是在类的拷贝/复制构造函数。将函数的形参定义为常量的好处:引用不产生新的变量,减少形参与实参传递时候的开销。由于阴影可能导出实参随形参的改变而改变,将其定义为常量引用可以消除这种副作用。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person(string name):name(name){}
    string name;
};
void test(const Person &p)
{
    cout << p.name << endl;
}
int main(int argc, char* argv[])
{
    Person p("aaa");
    test(p);
    return 0;
}

注意: 字面量不能赋给引用,但是可以赋值给const引用,const 修饰的引用不能被修改


内联函数


C++从C中继承的重要特征就是效率。假如C++效率明显低于C的效率,那么就会有很大的一批程序员不会去使用C++。在C中我们经常吧一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了 执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。


但是C++出现之后,使用预处理宏会出现两个问题:


在C中也会出现的问题,宏看起来像一个函数调用,但是会出现一些难以发现的错误。

C++特有问题, 预处理器不允许访问类的成员,也就是说预处理器宏不能用做类的成员函数。

为了保持预处理宏的效率又增加安全性,而且还能像一般程序员函数那样可以在类里访问自如,C++引入了内联函数(inline)


内联函数继承了宏函数的效率,没有函数调用时候的开销,然后又可以像普通函数那样,可以进行参数返回值的类型安全检查,又可以作为成员函数

内联函数和编译器

C++中,预定义宏函数是使用内联函数来实现的,而内联函数本身就是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同的是内联函数会在适当的地方像预定义宏一样展开,所以不需要调用开销。因此应该不适用宏,而使用内联函数。在普通函数前面加上inline使得其编程内联函数。但是必须注意,函数体和声明需要结合在一起,否则编译器会将它作为普通函数来对待。


类内部的内联函数


类内部函数时inline不是必须的,任何类内部定义的简单函数都可能会自动变为内联函数


内联函数限制


  • 不能存在任何形式的循环语句
  • 不能存在过多的判断语句
  • 函数体不能过于庞大,不能对函数进行取址操作


内联仅仅是给编译器一个建议,编译器不一定会接受这个建议,如果你没有将函数声明为内联函数,那么编译器也有可能将函数内联编译。


缺省参数和占位参数


缺省参数、


C++在声明函数原型的时候可以为一个或者多个参数指定默认(缺省)参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int x= 10, int y = 20)
{
    return x+y;
}
int main(int argc, char* argv[])
{
    cout << add() << endl;
    cout << add(50) << endl;
    cout << add(50, 60) << endl;
    return 0;
}


注意:


  • 函数默认参数从左到右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须 设置默认参数
  • 如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认类型参数
  • 如果函数声明和函数定义分开写,函数定义处的缺省参数时无效的,建议在声明处给默认参数
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int x = 10, int y = 10);
int main(int argc, char* argv[])
{
    cout << add() << endl;
    cout << add(50) << endl;
    cout << add(50, 60) << endl;
    return 0;
}
int add(int x, int y)
{
    return x+y;
}


占位参数


C++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名称声明。一般情况下,在函数体内部无法使用占位参数。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int, int, int);
int main(int argc, char* argv[])
{
    cout << add(50, 60, 100) << endl;
    return 0;
}
int add(int x, int y, int c)
{
    return x + y + c;
}

同样的,如果实现也不写参数名,则该参数必须传递,但是无法使用。


重载后置++的时候会用占位参数。


函数重载(overlaod)


同一个函数名在不同场景下可以具有不同的含义,重载使得函数名称使用更方便。

实现函数重载条件:同一个作用域,参数个数不同,参数类型不同,参数顺序不同

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int, int, int);
int add(int, int);
int main(int argc, char* argv[])
{
    cout << add(50, 60, 100) << endl;
    return 0;
}
int add(int x, int y, int c)
{
    return x + y + c;
}
int add(int x, int y)
{
    return x + y;
}


注意:

  • 函数的返回值类型不能作为函数重载的依据
  • 函数重载和默认参数在一起的时候会产生二义性问题


为什么返回值不作为重载条件呢?


因为我们在写程序的时候可以忽略函数的返回值,那么直接调用函数的时候编译器无法根据返回值类型来确定调用哪个函数,所以C++中禁止使用返回值类型做重载条件。


编译器在编译重载函数的时候会将重载函数名称修改成自己的函数名称。但是编译器没有统一标准,因此不同的编译器会产生不同的函数名称。


C++和C混合编程


c函数:void myfunc(){},被编译成myfunc

C++函数 void myfunc(){},被编译成:z6myfuncv


由于C++需要支持函数重载,所以C和C++中对同一个函数经过编译之后生成的函数名称是不一样的,这就导致了一个问题,如果在C++中调用一个使用C语言编写模块中的某个函数,那么C++是根据C++的名称修饰方式去查找并且连接这个函数的,这样会发生连接错误。


在C++中包含C库的时候需要将头文件使用extern “C”{}包含起来,这样对应的代码会用C编译器去编译,而不适用C++的方式。

目录
相关文章
|
2月前
|
设计模式 uml C++
C++中的装饰器模式:灵活地扩展功能
C++中的装饰器模式:灵活地扩展功能
47 0
|
2月前
|
算法 数据处理 C++
【C++ 20 新特性 算法和迭代器库的扩展和泛化 Ranges】深入浅出C++ Ranges库 (Exploring the C++ Ranges Library)
【C++ 20 新特性 算法和迭代器库的扩展和泛化 Ranges】深入浅出C++ Ranges库 (Exploring the C++ Ranges Library)
330 1
|
1月前
|
程序员 C语言 C++
【C++语言】继承:类特性的扩展,重要的类复用!
【C++语言】继承:类特性的扩展,重要的类复用!
|
2月前
|
算法 Java C++
【C/C++ 内存知识扩展】内存不足的可能性分析
【C/C++ 内存知识扩展】内存不足的可能性分析
28 0
|
2月前
|
存储 编译器 C++
C++新特性 扩展和聚合类型
C++新特性 扩展和聚合类型
|
2月前
|
安全 程序员 编译器
C++对C的扩展(上)
C++对C的扩展
39 0
|
11月前
|
安全 编译器 C语言
c++学习之c++对c的扩展1
c++学习之c++对c的扩展1
91 0
|
2月前
|
PyTorch 算法框架/工具 C++
windows上编译安装pytorch的c++扩展
windows上编译安装pytorch的c++扩展
|
8月前
|
存储 编译器 数据处理
c语言、c++扩展介绍 ————柔性数组、零长数组。
零长数组做为一种 GNU 的语法扩展方式,为数据处理提供优化支持。 因为编译器的编译特性,这种声明方式,只是一个指向固定位置的偏移量常量, 为什么要使用零长数组
52 0
|
9月前
|
算法 C++
剑指offer(C++)-JZ71:跳台阶扩展问题(算法-动态规划)
剑指offer(C++)-JZ71:跳台阶扩展问题(算法-动态规划)