C++中的布尔类型

C语言根本没有具体的bool类型存在,C语言往往使用整型0和1来代替bool类型。功能上可以,但是显得不那么严谨。

  1. C++在C语言的基本类型系统之上增加了bool
  2. C++中的bool可取的值只有true和false
  3. 理论上bool只占用一个字节,内部实现仍然使用整型,一个byte。

注意:
true代表真值,编译器内部用1来表示
false代表非真值,编译器内部用0来表示

bool类型只有true (非0 )和false ( 0 )两个值
C++编译器会将非0值转换为true,0值转换为false。为了兼容C语言,bool类型还支持数学运算

运行实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

int main(int argc, char *argv[])
{
bool b = false;
int a = b;

printf("sizeof(b) = %d\n", (int)sizeof(b)); //1
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("a = %d, b = %d\n", a, b); //10,1

a = 0;
b = a;

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

return 0;
}

如果用gcc编译,观察得到bool类型并不存在。证明了bool类型是C++特有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fengyun@ubuntu:~/share$ gcc test.c -o test
test.c: In function ‘main’:
test.c:5:5: error: unknown type name ‘bool’
5 | bool b = false;
| ^~~~
test.c:5:14: error: ‘false’ undeclared (first use in this function); did you mean ‘fclose’?
5 | bool b = false;
| ^~~~~
| fclose
test.c:5:14: note: each undeclared identifier is reported only once for each function it appears in


fengyun@ubuntu:~/share$ g++ test.c -o test
fengyun@ubuntu:~/share$ ./test
sizeof(b) = 1
b = 0, a = 0
b = 1, a = 1
b = 1, a = 1
a = 10, b = 1
a = 0, b = 0

布尔类型是C+ +中的基本数据类型

  • 可以定义bool类型的全局变量
  • 可以定义bool类型的常量
  • 可以定义bool类型的指针
  • 可以定义bool类型的数组

C++三目预算符

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(int argc,char *argv[])
{
int a = 1;
int b = 2;
(a < b ? a : b) = 3;
printf("a = %d, b = %d\n",a, b);
return 0;
}

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

1
2
3
4
5
6
7
8
9
fengyun@ubuntu:~/share$ gcc test.c -o test
test.c: In function ‘main’:
test.c:6:21: error: lvalue required as left operand of assignment
6 | (a < b ? a : b) = 3;
| ^


fengyun@ubuntu:~/share$ g++ test.c -o test
fengyun@ubuntu:~/share$

但是对于(a < b ? a : 2) = 3;gcc和g++编译时都将会报错

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

C++引用

变量名回顾

  • 变量是一段实际连续存储空间的别名
  • 程序中通过变量来申请并命名存储空间
  • 通过变量的名字可以使用存储空间

image-20220221180028228

一段连续的存储空间只能有一个别名吗?
C++由此增加了引用的概念

引用可以看作一个已定义的变量的别名

引用的语法:Type& name = var;

1
2
3
4
int a = 4;
int& b = a; //b为a的别名

b = 5; //操作b就是操作a

注意:普通引用在定义的时候必须用同类型的变量进行初始化

C++对三目运算符做了什么?
返回值:

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

引用的意义

引用作为变量别名而存在,因此在一些场合可以代替指针
引用相对于指针来说具有更好的可读性和实用性

image-20220221192212230

注意:函数中的引用形参不需要进行初始化

const引用【特殊】

  • 在C++中可以声明const引用
  • const Type& name = var ;
  • const引用让变量拥有只读属性
1
2
3
4
5
6
7
int a = 4;
const int& b = a;
int* p = (int*) &b;

b = 5;// Error ,只读变量

*p = 5;//Ok,修改变量a的值

当使用字面常量对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名

1
2
3
4
5
6
const int& b = 1; //ok
int* p = (int*) &b;

b = 5;// Error ,只读变量

*p = 5;//Ok,修改变量a的值

结论:使用常量对const引用初始化后将生成一个只读变量! ! !

一道面试题:引用有自己的存储空间吗?

我们知道指针是一个变量,是存储了一个地址的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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&) = %d\n", sizeof(char&)); //1
printf("sizeof(rc) = %d\n", sizeof(rc)); //1 等价于sizeof(c)

printf("sizeof(TRef) = %d\n", sizeof(TRef)); //
printf("sizeof(ref.r) = %d\n", sizeof(ref.r)); //1 等价于sizeof(c)

return 0;
}

引用

1
2
3
4
5
fengyun@ubuntu:~/share$ ./test
sizeof(char&) = 1
sizeof(rc) = 1
sizeof(TRef) = 8
sizeof(ref.r) = 1

引用的本质

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

image-20220221194227671

注意:

  1. C++编译器在编译过程中用指针常量作为引用的内部实现,因此引用所占用的空间大小与指针相同。
  2. 从使用的角度,引用只是一个别名,C++为了实用性而隐藏了引用的存储空间这一细节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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) = %d\n", sizeof(r));
printf("sizeof(r.before) = %d\n", sizeof(r.before));
printf("sizeof(r.after) = %d\n", sizeof(r.after));
printf("&r.before = %p\n", &r.before);
printf("&r.after = %p\n", &r.after);

return 0;
}
1
2
3
4
5
6
fengyun@ubuntu:~/share$ ./test
sizeof(r) = 24
sizeof(r.before) = 8
sizeof(r.after) = 8
&r.before = 0x7ffeeb46daf0
&r.after = 0x7ffeeb46db00

我在32位系统用VS2010反汇编查看汇编代码。

image-20220221195439564

  1. 将61h移动到[a]开始的一个字节大小的空间 //char a = ‘a’
  2. 将a标识符的地址取出来移入寄存器eax将寄存器 //char& b = a;
    eax中的值移入b标识符对应的4个字节大小的空间

通过对汇编代码的分析引用的本质是一个指针。

引用的意义

C+ +中的引用旨在大多数的情况下代替指针

  • 功能性:可以满足多数需要使用指针的场合
  • 安全性:可以避开由于指针操作不当而带来的内存错误
  • 操作性:简单易用,又不失功能强大

引用错误用例

不能返回局部变量的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int& demo() //int* const
{
int d = 0;

printf("demo: d = %d\n", d);

return d; //error
}
int& func()
{
static int s = 0;

printf("func: s = %d\n", s);

return s; //ok
}

引用的疑问

  • 指针是一个变量
    值为一个内存地址,不需要初始化,可以保存不同的地址
    通过指针可以访问对应内存地址中的值
    指针可以被const修饰成为常量或者只读变量

  • 引用只是一个变量的新名字
    对引用的操作(赋值,取地址等)都会传递到代表的变量.上
    const引用使其代表的变量具有只读属性
    引用必须在定义时初始化,之后无法代表其它变量

  • 从使用C++语言的角度来看
    引用与指针没有任何的关系
    引用是变量的新名字,操作引用就是操作对应的变量

  • 从C++编译器的角度来看
    为了支持新概念“引用”必须要一个有效的解决方案
    在编译器内部,使用指针常量来实现“引用”
    因此“引用”在定义时必须初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

int a = 1;

struct SV
{
int& x;
int& y;
int& z;
};

int main()
{
int b = 2;
int* pc = new int(3);
SV sv = {a, b, *pc}; //结构体未报错
//int& array[] = {a, b, *pc}; // &array[1] - &array[0] = ? Expected ==> 4

printf("&a = %p\n", &a); //&a = 0x555dfe26c010
printf("&b = %p\n", &b); //&b = 0x7ffd8ff7f294
printf("pc = %p\n", pc); //pc = 0x555dffdc2eb0

printf("&sv.x = %p\n", &sv.x); //&sv.x = 0x555dfe26c010
printf("&sv.y = %p\n", &sv.y); //&sv.y = 0x7ffd8ff7f294
printf("&sv.z = %p\n", &sv.z); //&sv.z = 0x555dffdc2eb0

delete pc;

return 0;
}

sv.x,sv.y,sv.z分别位于静态存储区,堆空间,栈空间,地址是不一样的。数组里面的每个元素应该是相邻排放的,但是引用数组破坏了相邻排放这个特性。因此C++里面不支持引用数组