【C++深度解析】3、布尔类型和引用

1 布尔类型

C++ 中的布尔类型

  • C++ 在 C 语言的基本类型系统上增加了 bool
  • bool 只占一个字节,可取的值只有 true(1)和 false(0)
  • 编译器将非 0 值转换为 true,0 值转换为 false

看下面的代码,bool 类型可以进行算术运算吗?在 C 语言没有 bool 类型,用 int 类型的 0 和 1来表示,所以 C 语言中可以进行算术运算。C++ 为了兼容 C 语言,也可以进行算术运算,运算结果为 0 时,赋值为 false,不为 0 时,赋值为 true。

在这里插入图片描述
上面的代码,首先 b 为 false,打印 0;b++ 后 b 为 1,不等于 0,打印 1;b = b - 3,得 b 为 -2,不为0,打印 1。

实例分析:布尔类型的使用

// 3-1.c
#include<stdio.h>
int main()
{
    bool b = false;
    int a = b;
    printf("sizeof(b) = %d\n", sizeof(b));	// 1,bool一个字节
    printf("b = %d, a = %d\n", b, a);		// 0 0

    b = 3;
    a = b;
    printf("b = %d, a = %d\n", b, a);		// 1 1

    b = -5;
    a = b;
    printf("b = %d, a = %d\n", b, a);		// 1 1
    
    a = 10;
    b = a;
    printf("b = %d, a = %d\n", b, a);		// 1 10
    
    a = 0;
    b = a;
    printf("b = %d, a = %d\n", b, a);		// 0 0
    return 0;
}

编译运行:

$ g++ 3-1.c -o 3-1
$ ./3-1
sizeof(b) = 1
b = 0, a = 0
b = 1, a = 1
b = 1, a = 1
b = 1, a = 10
b = 0, a = 0

2 三目运算符

下面的代码正确吗?
在这里插入图片描述

  • C 语言中的三目运算符返回的是变量值,不能作为左值使用
  • C++ 中的三目运算符可直接返回变量本身,既可作为右值使用,又可作为左值使用

!!注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用。

所以上面的代码用 gcc 编译器不通过,告诉你三目运算符是右值,不能当作左值使用。g++ 编译器可以编译运行。a = 3, b = 2。

3 C++ 中的引用

变量是一段实际连续存储空间的别名,那么一段连续的存储空间只能有一个别名?

  • 在 C++ 中新增加了引用的概念,引用可以看作一个已定义变量的别名
  • 引用的语法:Type& name = var;

注意:引用必须用同种类型的变量进行初始化

实例分析:引用初体验

// 3-2.c
#include<stdio.h>
int main()
{
    int a = 4;
    int& b = a;
    b = 5;
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("&a = %p\n", &a);
    printf("&b = %p\n", &b);
    return 0;
}

上面的代码,b 是变量 a的别名,操作 b 就是操作 a。

$ g++ 3-2.c -o 3-2
$ ./3-2
a = 5
b = 5
&a = 0x7ffe7604344c
&b = 0x7ffe7604344c

b 是 a 的别名,指向的是同一块地址空间,所以值相同,地址相同。

3.1 再看三目运算符

  • 当三目运算符的可能返回都是变量时,返回的是变量引用
  • 当三目运算符的可能返回中有常量时,返回的是值

在这里插入图片描述

4 引用的本质

4.1 特殊的引用

const 引用

  • 在 C++ 中可以声明 const 引用,让变量拥有只读属性
  • const Type& name = var;

在这里插入图片描述

  • 当使用常量对 const 引用进行初始化时(生成一个只读常量),C++ 编译器会为常量值分配空间,并将引用名作为这段空间的别名

在这里插入图片描述
!!!注意:不存在引用数组
因为数组中的元素是连续的,如果一个数组中的元素都是引用,引用的变量可以存储在任意位置,这样元素位置就不连续了,相互矛盾,所以 C++ 中不支持引用数组。

实例分析:引用的特殊意义

// 3-3.c
#include<stdio.h>
void Example()
{
    printf("Example:\n");
    int a = 4;
    const int& b = a;
    int* p = (int*)&b;
    // b = 5;			// 只读变量
    *p = 5;
    printf("a = %d\n", a);
    printf("b = %d\n", b);
}
void Demo()
{
    printf("Demo:\n");
    const int& c = 1;
    int* p = (int*)&c;
    // c = 5;			// 只读变量
    *p = 5;
    printf("c = %d\n", c);
}
int main()
{
    Example();
    printf("\n");
    Demo();
    return 0;
}

编译运行:

$ g++ 3-3.c -o 3-3
$ ./3-3
Example:
a = 5
b = 5

Demo:
c = 5

const 引用是只读变量,但是指向的存储空间不仅仅只有一个别名,可以通过指针修改,const 引用表示不能通过引用修改变量。

4.2 引用的存储空间

问题:引用有自己的存储空间吗?
在这里插入图片描述
下面我们就来通过实验验证一下这个问题。

// 3-4.c
#include<stdio.h>
struct TRef
{
    char& r;
};
int main(int argc, char *argv[])
{
    char c = 'c';
    char& rc = c;
    TRef ref = { c };
    printf("sizeof(char&) = %ld\n", sizeof(char&));
    printf("sizeof(rc) = %ld\n",  sizeof(rc));
    printf("sizeof(TRef) = %ld\n", sizeof(TRef));
    printf("sizeof(ref.r) = %ld\n", sizeof(ref.r));
    return 0;
}
  • 第 12 行,char& 就是 char 的别名,所以长度为 1
  • 第 13 行,rc 是 char 类型的引用,也就是 char 的别名,长度为 1
  • 第 14 行,sizeof(TRef) 是求解结构体中引用本身的大小,不是所指向的变量大小
  • 第 15 行,ref.r 的类型是 char 的引用,也就是 char 的别名,长度为 1

编译运行:

$ g++ 3-4.c -o 3-4
$ ./3-4
sizeof(char&) = 1
sizeof(rc) = 1
sizeof(TRef) = 8
sizeof(ref.r) = 1

得出引用本身的大小为 8,到底引用的本质是什么呢,我们继续分析。

4.3 引用的本质

  • 引用在 C++ 中的内部实现是一个常量指针

在这里插入图片描述
注意:

  • C++编译器在编译过程中用指针常量作为引用的内部实现,因此引用所占用的空间大小与指针相同
  • 从使用的角度,引用只是一个别名,C++ 为了实用性而隐藏了引用的存储空间这一细节

编程实验:引用的存储空间

// 3-5.c
#include<stdio.h>
struct TRef
{
    char* before;
    char& ref;
    char* after;
};
int main(int argc, char* argv[])
{
    char a = 'a';
    char& b = a;
    char c = 'c';
    TRef r = {&a, b, &c};
    printf("sizeof(r) = %ld\n", sizeof(r));
    printf("sizeof(r.before) = %ld\n", sizeof(r.before));
    printf("sizeof(r.after) = %ld\n", sizeof(r.after));
    printf("&r.before = %p\n", &r.before);
    printf("&r.after = %p\n", &r.after);
    return 0;
}

结构体中三个变量,两个指针,一个引用,打印结构体的大小,再打印结构体中指针的的大小和地址。

$ g++ 3-5.c -o 3-5
$ ./3-5
sizeof(r) = 24
sizeof(r.before) = 8
sizeof(r.after) = 8
&r.before = 0x7ffe5b219d10
&r.after = 0x7ffe5b219d20

两个指针的地址分别是 r.before = 0x7ffe5b219d10,r.after = 0x7ffe5b219d20,两个地址之差是0x10,就是16,指针占用8 字节,也就是说引用大小为 8 字节。

我们用 vs 编译器生成一下反汇编代码,如下所示:
在这里插入图片描述
由于 vs 按照32 位系统进行编译,所以指针大小为 dword,但是我们知道了引用其实本质上就是常量指针。

4.4 引用的弊端

C++ 中的引用旨在大多数情况下代替指针,可以避开由于指针操作不当而带来的内存错误,简单易用,又强大。

但是引用的本质是常量指针,不能避免指针的本质错误,千万不能返回局部变量的引用,因为局部变量生存期结束之后,内存中的数据被销毁,不能再操作了。

实例分析:函数返回引用

// 3-6.c
#include<stdio.h>
int& demo()
{
    int d = 0;
    printf("demo: d = %d\n", d);
    return d;
}
int& func()
{
    static int s = 0;
    printf("func: s = %d\n", s);
    return s;
}
int main(int argc, char* argv[])
{
    int& rd = demo();
    int& rs = func();
    printf("\n");
    //printf("main(): rd = %d\n", rd);
    printf("main(): rs = %d\n", rs);
    printf("\n");

    //rd = 10;
    rs = 11;
    demo();
    func();

    printf("\n");
    //printf("main(): rd = %d\n", rd);
    printf("main(): rs = %d\n", rs);
    printf("\n");
    return 0;
}

函数 demo() 中,d 是局部非静态变量,函数返回后,变量内存已经被摧毁,函数 func() 中,s 是局部静态变量,生存期和整个程序生存期相同,函数返回,变量值依然维护。

第 20、24、30 行操作函数局部的引用,导致段错误

$ g++ 3-6.c -o 3-6
3-6.c: In function ‘int& demo():
3-6.c:5:9: warning: reference to local variable ‘d’ returned [-Wreturn-local-addr]
     int d = 0;
         ^
$ ./3-6
demo: d = 0
func: s = 0

main(): rs = 0

demo: d = 0
func: s = 11

main(): rs = 11

返回局部非静态变量的引用时,编译器也给出警告。函数 func() 返回后,变量 s 依然有效。

5 小结

1、bool 类型只有 true 喝 false
2、C++ 中的三目运算符可作为左值使用
3、C++ 中的引用可以看作变量的别名
4、三目运算符的可能返回都是变量时,返回的是引用
5、引用作为变量别名而存在旨在代替指针
6、const 引用使得变量具有只读属性
7、引用本质为常量指针
8、不要返回局部非静态变量的引用

发布了248 篇原创文章 · 获赞 115 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/104034739