一、语法增强
1. 全局变量检测增强
c语言代码:
int a = 10; // 赋值,当作定义 int a; // 没有赋值,当作声明 int main() { return 0; }
没有报错,可以执行。
如果仅仅int a
,内存空间存在。
当单独int a
,会定义a
,赋初值0
;
当同时int a = 10; int a;
,第一条语句是定义,第二条语句仅声明。
c
是弱语法语言,没有问题。
但C++
中无法使用,重定义
2. 类型检测
void func(i) { i++; } // C中传参可以不写类型 C++中不可以,必须严格声明类型
3. 更严格的类型转换
在C++
中,不同类型的变量一般是不能直接赋值的,需要相应的转换。
二、 结构体增强
C
中定义结构体变量需要加上struct
关键字,C++
不用。C
中结构体只能定义成员变量,不能定义成员函数。C++
即可以定义成员变量,也能定义成员函数。
三、boolean 类型
bool
true
false
一个bool
类型一个字节,true
是1
, flase
是0
四、三目运算符的增强
C
语言的三目运算表达式返回值为数据值,为右值,不能赋值;C++
语言三目运算表达式返回值为变量本身(引用),为左值,可以赋值
左值和右值的概念
在C++
中可以放在赋值操作符左边的是左值,可以放到赋值操作符右边的是右值。有些变量可以当左值,也可以当右值。
左值是 Lvalue
, L
代表 Location
,表示内存可以寻址,可以赋值。
右值是 Rvalue
, R
代表 Read
, 就是可以知道它的值。
比如 int temp = 10;
中, temp
在内存中有地址, 10
没有,但是可以 Read到它的值。
五、const
C语言中的const
默认外部连接
// c 语言 的 `const' 修饰的全局变量 默认是外部连接的 // 外部连接: 其他源文件 可以使用 // func.c const int num = 100; // 只读的全局变量 // main.c extern const int num; // 声明 不赋值 printf("%d\n", num); // 输出 100 // 如果是全局变量,文字常量区, 只读 // 局部变量 栈区 内存可读可写 // 局部只读变量可以通过地址修改 // 但: 如果知道num的地址,可以通过地址间接地修改num的值 const int data = 100; int *p = (int *)&data; *p = 200; printf("%d", data); // 200
C
中的const
修饰全局变量 默认是 外部连接的局部可读变量可以通过地址修改
总结
在C语言中:
const
修饰全局变量num
变量名只读,内存空间在文字常量区(只读)、不能通过num
的地址修改空间内容;const
修饰局部变量data
变量名只读,内存空间在栈区(可读可写),可以通过data
地址 间接修改空间内容;
C++ 中的 const
默认内部连接
// func.cc const int num = 100; // 默认内部连接 extern const int num = 100; // 设定为外部连接
赋值常量写入符号表
尝试修改值,而不会被修改:
const int data = 100; int *p = (int *)&data; *p = 2000; cout << "data = " << data << endl; // data 的值还是 10 cout << "p = " << *p << endl;
调试状态下,内存空间会默认被开辟:
此时, data
和 p
指向同一片空间,而修改时,两者依旧相同:
值被修改了!
打印的时候却没有修改。
const
声明的 data
,实际上并不在那片内存空间上!
C++
中会对 const
的变量创建一个符号表,直接读取,而当要取出该变量的时候, C++
编译器会开辟一片空间。而通过变量名访问的时候,还是从符号常量表中查到该值。
开辟空间的情况
申请常量的地址时;
用一个普通变量赋值到常量时:
int b = 200; const int a = b;
完成赋值,两者值相同,接下来更改内容:
由于
p
a
指向的地址相同,修改p
时就会修改a
所属于的值。按照上一种情况,符号常量表中会存储a
, 访问a
时会直接去找符号常量表的内容。但是:我们可以看到,
a
的值被改变了!很明显,这种常量定义的情况,符号表不会存储定义的变量,而是内存开辟一片存储内容,而这片内容可以通过地址间接修改。
如果
b
也是常量呢?const int b = 100; // b 在符号表中 const int a = b; // a 也会到符号表中 相当于 a 就是 b
输出结果:
如果将一个常量赋值给一个非常量呢?
const int b = 100; int a = b; // 这个最终结果和 前面是一样的 // 也就是说 int b = 100; const int a = b; // 两种情况下 对于 a 来说内存是可变的 // 但注意 const 声明的变量是不能直接修改的
自定义数据类型,会分配内存 系统会分配空间(结构体、对象)
符号表只会记录简单的数据类型
建议:
总结
const int data = 10;
// data 先放入符号表- 如果对
data
取地址 系统才会给 data 开辟空间 const int a = b;
// b 是变量名 系统直接给a
开辟空间 而不放入符号表const
修饰自定义数据 系统为自定义数据开辟空间
六、补充
1. 尽量使用const
替换#define
C++
尽量 使用 const
替换 #define
#define
仅仅替换, 很难查找到错误
const
有作用域
宏不能作为命名空间的成员 const 可以
七、引用(reference)
给已有变量取别名
1. 语法:
- &和变量名结合 表示 引用
- 给某个变量取别名 就定义某个变量
- 从上往下替换
int num = 10; int &a = num; // 引用必须初始化 int *b = # // 取地址 // a 完全等价于 num
2. 注意:
引用必须初始化
引用一旦初始化,就不能再次修改别名
int num = 10; int &a = num; int data = 20; a = data; // 不是 data 别名 为 a, 而是 将 data 值赋值 a(num)
3. 一些探讨
再尝试查找对应的地址:
int data = 100; int &a = data; int &b = data; int &c = a;
此时,a
b
c
都指向 data
的地址 。
尝试加入一个 p
指针,对比与引用的区别:
可以看出,引用虽然在理解上是起别名,但在实际实现上就是存储地址,当使用到该别名的时候,就会通过该变量中指向的地址去找变量。
而指针是直接指向所需变量的地址,引用相当于在指针上加一层封装,最终的效果还是找到值存在的地址。
引用在底层上实现了它就类似一个变量,而实际上是一个指向所引用对象的地址。
引用并非指针,指针直达地址,更快,引用中存储地址,有一层封装。但相对来说,引用不是直接操作指针,地址不可修改,而指针可修改。相对来说,引用更安全。
但是程序取其地址的时候:
三个变量指向的地址是一样的,这是一种设计。实际,引用变量实际地址在内存中并非是相同的地址,而是包装一个指向所引用变量的地址,当取地址时,会返回该地址。
所以,引用是占用内存空间的!但引用比直接使用指针安全。
给数组起别名
int arr[10] = {0}; int (&my_arr)[10] = arr; // int &my_arr[10] 每个元素都是一个引用 typedef int TYPE_ARR[10]; TYPE_ARR &my_arr_2 = arr;
函数的引用
引用作为函数的参数
// 写一个交换函数 void my_swap(int &a, int &b) { int tmp = a; a = b; b = tmp; } // 内部做的操作
引用作为函数的返回值
注意:
- 函数返回值是引用时,不要返回局部变量
- 当函数为左值时,那么函数值类型必须是引用
// 当函数为左值时,那么函数值类型必须是引用 int& my_data() { static int num = 10; cout << "num = " << num << endl; return num; } void test() { my_data() = 2000; my_data(); } // 主要用于运算符重载。
引用的本质是常量指针
引用的本质在 C++
内部实现是一个指针常量。
Type& ref = val
,实际上是 Type* const ref = &val
C++
编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现的,用户不可见。
int data = 10; int &a = data; // 编译器内存转换 int * const a = &data; // 不能改变 a = 100; // 实际的处理过程交给编译器做
在手动复制的过程中,两者的地址是一样的。但如果直接使用引用,调试状态下的引用变量的地址是不一样的。(疑惑?)
指针的引用
char* &mystr;
常引用
*引用没有开辟独立的空间???
sizeof
无法测试引用的 空间 *一旦引用代表别名,则测试引用实际上是测所引用的变量。
常引用的格式:
const Type& ref = val;
注意: 字面量不能赋给引用,但是可以赋给常引用。const
修饰的引用,不能修改。
使用场景: 常引用主要用在函数的形参,尤其是类的拷贝或复制构造函数。
*将函数的形参定义为常引用的好处: * 引用不产生新的变量,减少形参与实参传递时的开销。由于引用可能导致实参随形参改变而改变,将其定义为常引用可以消除这种副作用。如果希望实参随着形参的改变而改变,那么使用一般的引用。
常量的引用:
// 10 是 const int 类型 const int &num = 10;