C++入门篇——深入C++基础语法(三)

C++是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度抽象和建模时,C语言则不合适。为了解决软件危急,20世纪80年代,计算机界提出了OOP(面向对象)思想,支持面向对象的程序设计语言应运而生。

本篇将为大家介绍C++的基础语法,由于C++向下兼容C语言的大多数语言特性,对于一些C语言已具备的语法,将不做论述

前文索引:
C++入门篇——深入C++基础语法(一)

C++入门篇——深入C++基础语法(二)

目录

五、引用

引用的概念

引用的特性

常引用

使用场景

传值、传引用效率比较

引用和指针的区别

六、内联函数

内联函数的概念

内联函数的特性

七、auto关键字

auto的使用细则

auto不能推导的场景

八、基于范围的for循环

范围for的语法

范围for的使用条件

九、指针空值nullptr

十、extern"C"


五、引用

引用的概念

引用,即给已经存在的变量取一个别名,与所引用的变量共用一块内存空间,而不会单独开辟新的内存空间。

使用方法:类型& 引用变量(对象名) = 引用实体;

void test()
{
    int a = 1;
    int& b = a;//定义引用类型,引用在语法层,没有开辟空间,而是对原空间起了新的名字
}

引用类型必须和引用实体是同种类型的

引用的特性

1、引用在定义时必须初始化

2、一个变量可以有多个引用

3、引用一旦引用了一个实体,就不能再引用其它实体

常引用

先来看一看下面的代码:

const int a = 1;
int& ra = a;

这样写法是否正确呢?我们知道const使得变量a具有了常属性,不能再被修改。而int&定义的引用,应该是int类型的,是可以被修改的。这样一来,只可读的变量变成了可读可写,属于权限放大,是不被允许的。

const int a = 1;
const int& ra = a;

上面的代码属于权限不变,是正确的。

那么是否存在权限缩小呢?再来看看下面的代码:

int b = 1;
const int& rb = b;

该情况属于权限缩小,也是正确的。

因此,在使用引用时,权限不能放大。

下面代码又为什么会出错呢?

double c = 1.1;
int& rc = c;

其实截断和提升时不是对本身操作,而是要通过临时变量,临时变量具有常性。rc实际上是这个临时变量的引用,而非c的引用。

因此想使上述代码正确,应改为const int& rc = c;

结论:const Type& 可以接收各种类型的对象。使用引用传参,如果函数中不改变参数的值,建议使用const Type&

使用场景

1、做参数

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

传引用和传值可以构成函数重载,因为类型不同。但是调用时存在歧义,因为不知道调用传值还是传引用。

2、做返回值

int& count()
{
    static int n = 0;
    n++;
    return n;
}

传值返回返回的是变量的拷贝,若变量较小(4 or 8bit),一般寄存器充当临时变量;如果比较大,则放在调用该函数的栈帧中。

引用返回的意思是不生成变量的拷贝返回,直接返回变量的引用

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

int main()
{
    int& tmp = add(1, 1);
    
    return 0;
}

当前代码的问题:

1、存在非法访问,因为函数的返回值是c的引用,所以add函数栈帧销毁了以后,会去访问c位置的空间。

2、如果函数栈帧销毁,清理空间,那么tmp的取值就是随机值。是否清理销毁空间的值,取决于编译器的实现。

传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或返回值类型非常大时,效率就更低。

引用的作用体现在传参与返回值

1、有些场景下,可提高性能(大对象+深拷贝对象)

2、输出形参数和输出形返回值(有些场景下,形参的改变可以改变实参;引用返回,可以改变返回对象)

引用和指针的区别

从语法上来说,应用就是已知变量的别名,是没有独立空间的。

但从底层实现来看,引用也是依靠指针的方式实现的。想验证这一点,就要从其汇编代码来观察。

int main()
{
    int a = 1;
    int& ra = a;
    int* pa = &a;

    return 0;
}

转到反汇编:

C++入门篇——深入C++基础语法(三)_第1张图片

 可以看到在汇编的情况下,引用与指针的操作步骤一模一样。

六、内联函数

内联函数的概念

对于频繁调用的小函数,能否进行优化?针对这个问题,C语言可以使用宏来解决。而C++提供了另一种方法——内联函数。

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

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

内联函数的特性

1、inline是一种以空间换时间的做法,省去调用函数的额外开销。所以代码很长或者有循环/递归的函数不适宜作为内联函数使用

2、inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略掉内联

3、inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

七、auto关键字

auto的使用细则 

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

auto a = 1;
auto a = 'a';

 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

细则:

1、auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

2、在同一行定义多个变量

当在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器只对第一个类型进行推导,然后用推导出来的类型定义其它变量

auto不能推导的场景

1、auto不能作为函数的参数

2、auto不能直接用来声明数组

3、为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

4、aoto在实际中最常见的优势用法是与C++提供的新式for循环与lambda表达式等进行配合使用

八、基于范围的for循环

范围for的语法

范围for的使用方式如下:

#include 
using namespace std;

int main()
{
    int arr[] = { 1,2,3,4,5,6,7 };
    for (auto e : arr)
    {
        cout << e << endl;
    }

    return 0;
}

运行过后就会依次打印该数组。

折断代码的含义是依次将数组中的每一个值赋值给e进行操作。

范围for的使用条件

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

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

值得注意的是,当数组名作为函数参数,经过传参,就不能使用该方法对数组进行遍历了,因为数组名被降级成为了指针。

2、迭代的对象要实现++和==的操作

九、指针空值nullptr

在C语言中,如果一个指针没有合法的指向,我们通常会将它赋值为NULL

NULL实际上就是一个宏:#define NULL        ((void *)0)

因此在使用NULL时,会出现一些麻烦:

#include 
using namespace std;

void test(int)
{
    cout << "int" << endl;
}

void test(int*)
{
    cout << "int*" << endl;
}

int main()
{
    test(NULL);

    return 0;
}

函数本意是通过NULL调用第二个test函数,但实际上,NULL被定义为0,导致匹配出错,无法正确调用函数。

针对这个问题,C++11给出指针空值nullptr

注意:

1、在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2、在C++11中,sizeof(nulllpt)与sizeof((void*)0)相同

3、为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

十、extern"C"

在工程中,我们可能会使用C++程序调用C的静态库,也可能使用C程序调用C++的静态库。如果我们不加改动直接调用,就会导致编译失败。这是因为C和C++函数的名字修饰不同,导致不能相互找到对应函数的地址。这时候extern"C"就排上了用场。

1、C++程序调用C库。在C++程序中加extern"C"

引头文件时:

//告诉C++编译器,大括号内的函数是用C编译器编译的,链接时要用C的函数名修饰规则去找
extern"C"
{
	#include "../../test2/test.h"
}

2、C程序调用C++库。在C++库中加extern"C"

在C++程序的头文件中:

_cplusplus是C++程序会自动生成的,利用这点,我们可以使用如下写法:

#pragma once

#ifdef _cplusplus
extern"C"
{
#endif

	void func(int a);

#ifdef _cplusplus
}
#endif

这段代码在.cpp文件中会展开extern"C",而在.c文件中调用该头文件时不会展开extern"C",保证代码编译不会出错

入门篇思维导图:

C++入门篇——深入C++基础语法(三)_第2张图片

 

你可能感兴趣的:(大师之路(C++),c++,开发语言,后端)