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、不要返回局部非静态变量的引用