【C++从0到1】第二篇:C++入门(下)


一、 extern “C”

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决。

1.1 C++调用C

假设现在我们在C++程序中实现一个函数需要调用一个栈。看下面代码:

#include <iostream>
using namespace std;

//给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
//不需要看懂这个函数实现的功能。只用知道这个函数实现需要用到栈
bool isValid(char* s)
{
    ST st;
    StackInit(&st);
    while (*s)
    {
        //判断是否是左括号
        if (*s == '{'
            || *s == '['
            || *s == '('
            )
        {
            stackPush(&st, *s);
            s++;
        }
        //为右括号出栈匹配
        else
        {
            //如果没有左括号,一上来就是右括号。
            if (StackEmpty(&st))
            {
                StackDestroy(&st);
                return false;
            }
            STDataType top = StackTop(&st);
            stackPop(&st);
            //匹配失败
            if ((*s == ')' && top == '(')
                || (*s == '}' && top == '{')
                || (*s == ']' && top == '[')
                )
            {
                StackDestroy(&st);
                return false;
            }

            s++;
        }

    }

    bool top = StackEmpty(&st);
    //都匹配成功了
    return true;
}

int main()
{
    char str[10] = {'{','(', '[', ']', ')', '}'};

    int ret = isValid(str);
    cout << ret << endl;

	

	return 0;
}


在这里插入图片描述
很显然,我们编译会出错因为我们在这个cpp工程当中找不到实现栈的库,那么我们就引入一个库,里面包含栈就可以了
在这里插入图片描述
此时我们新建一个工程,里面就是用c实现的栈,那么我们让其文件去调用这个库呢?我们以VS2019举例:
在这里插入图片描述
我们打开文件路径,此时就生成了一个静态库:
在这里插入图片描述
然后就在cpp工程中去调用它:
在这里插入图片描述
然后在进行配置:
在这里插入图片描述
在这里插入图片描述

我们去编译:看会不会编译成功呢?
在这里插入图片描述
这是为什么呢?这是一串看不懂的符号又是什么呢?此时我们发现这个错误是链接错误(LNK),但是我们明明有栈的实现啊,为什么还会发生链接错误呢?相信大家很快就能够发现问题,这是因为我们调用的是c实现的栈:
在这里插入图片描述
在这里插入图片描述

而此时我们在cpp中包含了这个栈的头文件,.但是我们在去调用c实现的栈的函数的时候,链接是采用的cpp方式链接的,但是在链接的时候却找不到这些符号,所以就会报错。如果大家学过程序的编译、链接就知道在代码在编译成可执行程序的过程中,是需要预处理、编译、汇编和链接,我们这里就不多讲,只需要知道在编译的时候会进行符号汇总,此时这些符号就会被汇总起来,在汇编的时候就会形成符号表,在链接的时候就会拿到这些符号去找函数的地址,但是此时cpp里面的函数像StackInit(?),去找函数地址的时候就会找不到。

那么我们将这个栈改为cpp实现的呢?可不可以呢?
在这里插入图片描述
我们在编译运行:
在这里插入图片描述
就可以了,这理解就比较简单了吧,我们在链接的时候,去找这些符号的时候,用cpp的方式就找到了。但是此时是c实现的栈我们有没有方法解决这个问题呢?
在这里插入图片描述
我们就可以采用extern “C”
在这里插入图片描述
再编译运行:
在这里插入图片描述
就可以了,那么extern “C”的作用就是告诉C++编译器,我里面的函数是c编译器编译的,链接的时候用c的函数名规则去找,就可以链接上。

1.2 C调用C++

此时我们将这个Test.cpp改为Test.c
在这里插入图片描述
我们同样需要一个实现栈的库:
在这里插入图片描述
这个栈的库是用c实现的。我们在编译运行Test.c:
在这里插入图片描述
没有错,这个理解起来就很容易了。但是我们将栈的实现改为cpp的呢?
在这里插入图片描述
我们再编译Test.c:
在这里插入图片描述
就报错了,同样也是报的链接(LNK)错误,但是发现此时这些符号和我们之前用c++掉c时候的报错是不一样的:
在这里插入图片描述
这也就进一步说明了在链接通过这些符号去找函数地址的时候,c和c++都会按照自己的方式去找。
此时我们也用extern“C”来该代码:
在这里插入图片描述
我们编译Test.c:
在这里插入图片描述

为什么这些函数还是没有定义,并且还有一个语法错误,这是为什么呢?此时我们注意,我们是在Test.c中编译的时候包含了头文件<Stack.h>,而预处理的时候就会把里面的代码展开,extern "C"也会被展开,但是在Test.c中并不认识extern “C”,所以就会报错。

怎么解决呢?我们可以用条件编译:
在这里插入图片描述
这里的条件编译的意思就是如果在cpp当中,就会将extern "C"替换为EXTERN ,如果没有在cpp当中就会被替换成空格。我们不想让extern "C"在Test.c中出现,那么就会将EXTERN替换为空格
此时我们编译运行Test.c:
在这里插入图片描述
此时这个问题就解决了。我们还有另外一种解决方法:
在这里插入图片描述
这个又是什么意思呢?同样是如果是在cpp当中,就会有__cplusplus,第一次条件判断,将extern “C” { 包含进来,第二次条件判断,就将 } 包含进来。如果不是在cpp当中就什么都不做。
此时我们编译Test.c:
在这里插入图片描述
同样也可以运行成功。

二、内联函数

2.1 概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
这是什么意思呢?

#include <iostream>
using namespace std;

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//调用Add的次数较多
	Add(1, 2);
	Add(1, 2);
	Add(1, 2);
	Add(1, 2);
	Add(1, 2);
	Add(1, 2);
	return 0;
}

多次调用Add函数是会不断的开辟函数栈帧的,开辟函数栈帧消耗就比较大。
在这里插入图片描述

那么能不能优化一下呢?向这种小函数,我们就可以用宏实现来优化:
在这里插入图片描述
Add函数就会被直接替换。
在这里插入图片描述
但是一般使用宏的时候,直接替换,很容易出错。那么还有没有方法呢?答案是有的
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
    2.在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式

在这里插入图片描述
在这里插入图片描述
此时我们看到就没有调用函数了。
在这里插入图片描述

2.2 特性

  1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长(大概10以上)或者有循环/递归的函数不适宜使用作为内联函数。
  2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
    这是什么意思呢?比如:现在有Add声明
    在这里插入图片描述
    Add定义;
    在这里插入图片描述
    此时我们去调用它就会出错。
    在这里插入图片描述

面试题
宏的优缺点?
优点:
1.增强代码的复用性 。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
4. 常量定义 换用const
5. 函数定义 换用内联函数

三、auto关键字(C++11)

3.1 auto简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它。
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
举例:
在这里插入图片描述
实际当中一般不是这样使用。
注意:
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
在这里插入图片描述

3.2 auto的使用细则

1.auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
在这里插入图片描述
在这里插入图片描述
2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
在这里插入图片描述

3.3 auto不能推导的场景

  1. auto不能作为函数的参数
    在这里插入图片描述
  2. auto不能直接用来声明数组
    在这里插入图片描述
  3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
  4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

四、基于范围的for循环(C++11)

4.1 范围for的语法

在C++98中如果要遍历一个数组,可以按照以下方式进行:
在这里插入图片描述
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
在这里插入图片描述
在这里插入图片描述

4.2 范围for的使用条件

  1. for循环迭代的范围必须是确定的
    对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
    注意:以下代码就有问题,因为for的范围不确定
    在这里插入图片描述
  2. 迭代的对象要实现++和==的操作。(暂时不了解)

五、指针空值nullptr(C++11)

5.1 C++98中的指针空值

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
在这里插入图片描述
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
在这里插入图片描述
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
在这里插入图片描述
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的
    在这里插入图片描述
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小唐学渣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值