C++之苦海无涯

C++之苦海无涯,第1张

所谓苦海无涯,回头是岸。这是我跨入类与对象前的最后一篇C++博客了,再往后,就真的是入门级别的知识点了。

关键字:内联函数,auto关键字,范围for循环,指针空值nullptr。

目录

内联函数

宏的优缺点

内联函数的概念

内联函数特性 

auto关键字(C++11)

概念

 使用细则

 无法使用的场景

范围for循环(C++11)

范围for循环的使用条件

指针控制nullptr(C++11)


内联函数 宏的优缺点

什么是宏?

# 代表命令为预处理命令

define 为宏定义命令

#define 是比较常用的预处理命令,允许把参数替换到文本中,所以通常称之为宏定义,简称为。在预处理阶段将出现宏名的地方替换为定义的文本,称为宏替换。

宏定义的优点:

1.与类型无关

2.增强代码的复用性,方便修改

3.提高了程序的运行效率:

        使用带参宏定义既可以完成函数调用的功能,又能避免函数的出入栈 *** 作,减小系统开销,从而优化了运行效率。

宏定义的缺点:

1.不方便调试,因为预处理阶段进行了替换 

2.导致代码可读性差,可维护性差,容易误用

3.没有类型安全检查

4.可能会导致运算符优先级的问题,导致达不到预期结果

宏的替代品:

1.在常量定义方面,换用const

2.在函数定义方面,换用内联函数 

内联函数的概念

什么是内联函数?

C++中被inline关键字修饰的函数被称为内联函数。

内联函数可以降低程序的运行时间。当内联函数收到编译器的指令时,即可发生内联:编译器使用函数的具体定义来替代函数的调用语句。这种替代行为发生在函数的编译阶段而非运行阶段。

int add(int a, int b) {
    int ret = a + b;
    return ret;
}

 

inline int add(int a, int b) {
    int ret = a + b;
    return ret;
}

通过以上两种格式的代码产生的汇编可以看出,使用inline关键字修饰的函数在编译时并没有进行函数调用,而是按照其函数体的展开直接使用。

省去了函数调用的资源开支,所以内联函数可以有效地提高程序效率。

 在 Visual Studio 上想要达到这样的效果需要设置一些调试属性:

调试信息格式

内联函数拓展

内联函数特性 

 需要注意的时,内联函数只是作为用户对编译器的建议,是否内联取决于编译器自身对于函数的判断。

若函数符合内联的有利条件,编译器便会将函数内联。

如果函数体非常大,或者较为复杂,那么编译器会忽略用户对函数的内联建议,将内联函数作为普通函数处理。

#include 
#include 

using namespace std;

inline void swap(int& r1, int& r2) {
    int tmp = r1;
    r1 = r2;
    r2 = tmp;
}

inline void mysort(vector& arr) {
    int len = arr.size();
    for (int i = 0; i < len; ++i) {
        for (int k = 0; k < len - i - 1; ++k) {
            if (arr[k] > arr[k + 1]) {
                swap(arr[k + 1], arr[k]);
            }
        }
    }
}

 

 可见我们的排序代码由于其内部逻辑较为复杂,即使我们的函数使用了inline关键字进行修饰,编译器依旧将其按照函数进行了调用,而不是内联。

inline本质上是一种以空间换时间的做法,省去调用函数的资源开销,所以代码很长,或者函数内部有循环或者递归的函数不适宜作为内联函数。  

虽然内联函数只是对编译器的建议,但编译器对于内联的判断还是有些宽松,有些时候,执行函数体内代码的时间相比于函数调用的开销更大,如此情况下使用内联函数的收益会很少。

另外,内联函数的调用都要复制代码,如果某个函数代码量较大且使用次数很多,使用内联函数的话会导致程序的总代码量飞速增大,消耗更多的内存空间,得不偿失。 

所以,以下情况不适合使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

内联函数的声明与定义不建议分离,或者说内联函数的声明与定义必须在同一个文件中。

对于内联函数的inline关键字究竟是修饰函数声明还是函数定义,各种资料众说纷纭:

摘自C++ Primer的第三版

在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联。

摘自高质量C++/C 编程指南

关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。 

可能是由于它们所基于的C++版本不同?所以我们最好不要分离内联函数的声明与定义。

但至少有一点是可以肯定的,即,内联函数的声明与定义必须在同一个文件中。

如果像普通的函数一样将内联函数的声明与定义分散在头文件与源文件中,由于内联函数会在调用处被展开,如此便没有了函数地址,从而导致链接错误。

auto关键字(C++11) 概念

作为C/C++的元老级关键字,auto在C++11之前几乎没有什么存在感。

C++11之前的auto被解释为一个自动存储变量的关键字,也就是申明一块临时的变量内存。

这描述好像有些难以理解,但如果举出例子就好明白多了:

        我们在程序中所使用的不被任何关键字修饰的变量,都默认具有auto属性。

所以,auto关键字不被使用,却无处不在。但这就更凸显出这个关键字的鸡肋了。

所以,在C++11中,C++标准委员会赋予了auto全新的含义:auto作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导获得。

#include 

using namespace std;

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

int main() {
    int a = 10;
    auto b = a;
    auto c = 'c';
    auto d = 1.2;
    auto e = 1.3f;
    auto f = add(3, 2);

    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    cout << typeid(e).name() << endl;
    cout << typeid(f).name() << endl;

    return 0;
}

 可见C++11中的auto关键字可以自动推导出变量的类型。

注意:

使用auto定义变量时必须对变量进行初始化,因为编译器需要根据初始化表达式来推演变量的具体类型。所以,auto并不是一种类型的声明符号,它更类似于一种“占位符”,提前占据了位置然后由编译器在编译期间为其赋予具体的类型。
 使用细则

1. auto 与 auto* 没有任何区别,但auto声明引用类型时必须加上&

#include 

using namespace std;

int main() {
    int a = 10;
    auto b = &a;
    auto* c = &a;
    auto d = a;
    auto& e = a;

    cout << a << endl;

    *b = 20;
    cout << a << endl;

    *c = 30;
    cout << a << endl;

    d = 40;
    cout << a << endl;

    e = 50;
    cout << a << endl;

    return 0;
}

 2.使用auto同一行定义多个变量时,同一行变量类型必须相同。

#include 

using namespace std;

int main() {
    auto a = 'c', b = 'd';
    //auto c = '1', d = 10;
    return 0;
}

 这个可以通过编译。

#include 

using namespace std;

int main() {
    //auto a = 'c', b = 'd';
    auto c = '1', d = 10;
    return 0;
}

 无法使用的场景

1.auto不能作为函数参数

int add(auto a) {
    return a + a;
}

   

2.auto不能声明数组 

int main() {
    auto a[] = { 3,4,5 };
    return 0;
}

3.为了与C++11之前的auto的用法划清界限,C++11仅保留了auto作为类型指示符的用法。 

范围for循环(C++11)

我们平时遍历数组等一系列数据集合的时候,一不小心就会搞错其数据长度,然后导致一系列令人头疼的结果。并且,分明编译器自身是知道集合的大小,循环的范围该如何取,却偏偏令我们取绞尽脑汁,在危险边缘跳舞,实属多余。

对于这种情况,C++11引入了基于范围的for循环。

for循环后括号中的内容由冒号“ : ”分为了两部分,第一部分是范围内用于迭代的变量,第二部分表示迭代的范围。

for ([用于迭代的变量] : [迭代的范围])
#include 

using namespace std;

int main() {
    int a[] = { 1,2,3,4,5 };
    for (int& i : a) {
        cout << i << ' ';
    }
    return 0;
}

 运行结果:

 当然,与普通循环类似,在范围for循环中也可以用 continue 来结束本次循环,使用 break 来跳出整个循环。

范围for循环的使用条件

1.for循环的迭代范围必须是确定的。

        对于数组而言,就是数组的第一个元素到最后一个元素;对于类而言,需要提供begin和end方法,begin和end就是for循环迭代的范围。

2.迭代的对象要实现++或== *** 作。

指针控制nullptr(C++11)

在传统的C语言中,对于NULL的定义是这样的:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

NULL可能是常量0,也有可能是无类型指针(void*)的常量,由此可能会导致一些不必要的麻烦。

而在C++98中,字面常量0既可以是一个整型数字,也可以是无类型指针(void*)常量,但编译器默认为整型常量,如果按照指针方式使用,还需要强制类型转换。

为了避免所述麻烦,C++11中引入了新的关键字nullptr来表示指针空值。

注意:

sizeof( nullptr ) 与 sizeof( (void*) 0 ) 所占字节数相同

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/676376.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-04-19
下一篇2022-04-19

发表评论

登录后才能评论

评论列表(0条)

    保存