1、C++对C的扩展
(1)命名空间(作用域):namespace
在C中,只有一个全局作用域,C语言的所有的全局标识符共享同一个作用域,标识符之间可能发生命名冲突。
C++中提出了命名空间的概念,命名空间将全局作用域分成不同的部分,不同命名空间中的标识符可以同名而不会发生冲突,命名空间可以相互嵌套,全局作用域也叫默认命名空间。
在C++中,名称name可以使符号常量、变量、宏、函数、结构、枚举、类和对象等,在大规模程序设计中,以及在程序员使用各种各样的C++库时,这些标识符发生“命名冲突”,标准C++引入了namespace关键字,可以很好地控制标识符的作用域。
namespace是指标识符的各种可见问题。C++标准程序库中的所有标识符(形如:endl/cout/cin等)都被定义在一个名为std的namespace中。
命名空间定义:namespace name { ... }
命名空间使用:using namespace name;
(2)regiter关键字的增强
“请求”编译器让局部变量a直接放在寄存器里面,速度快。
//1.在c语言中register修饰的变量不能取地址,但是在C++编译器中可以取地址,在C++编译器中动了手脚。
//2.C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。
//3.早期的C语言编译器不会对代码进行优化,因此register变量是一个很好地补充。
(3)C++中,不允许定义多个同名的全局变量。
int a;
int a = 0; //在C语言中编译可以成功,但是在C++中编译会失败。
【总结】
C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上。
C++直接拒绝这种二义性的行为
(3)struct类型加强
(4)C++所有变量和函数必须有类型(对类型检查将会更加严格)
结论:
C语言中的默认类型在C++中是不合法的。
在C语言中:
int f();表示返回值为int,接受任意参数的函数
int f(void);表示返回值为int的无参函数
在C++中:
int f();和int f(void);具有相同的意义,都表示返回int的无参函数
C++更加强调类型,任何程序元素都必须显示的指明类型。
(5)C++引出bool类型
bool类型只有两个值:1和0
bool类型占用1个字节
(6)三目运算符在C和C++中的不同:C++对三目运算符做了优化。
在C中:三目运算符不可以作“左值”,它返回的是一个值
(a<b?a:b) = 30; //错误
在C++中:三目运算符可以作“左值”,C++编译器对它动了手脚,它返回的是一个变量(但是:如果三目运算符的a、b中有一个是常量值,则不能作为左值)
(a<b?a:b) = 30; //正确
做了什么手脚: *((a<b?&a:&b)) = 30; //正确
(a<b?a:200) = 30; //错误
【注解】当左值的条件:这段内存空间可以被写。
(7)const关键字
#include<iostream>
using namespace std;
int main()
{
const int a;
int const a; //二者意义相同
const int *c;
int const *c; //二者意义相同
int *const d;
const int* const e;
}
=====================================================
一般的const经常用作修饰结构体形参,防止结构体指向
中的变量被修改
因此,我们先介绍下结构体作为形参的下面两种case:
=====================================================
再次复习,深入了解:结构体作为形参、结构体的指针作为形参
#include<iostream>
using namespace std;
struct student
{
int age;
char name[100];
};
void f1( struct student stu) //结构体作为形参
//传入的是结构体变量,此时:相当于 “形参=实参” 的赋值操作,相当于浅拷贝
//在f1中对形参进行修改,并不会导致实参的值发生任何变化
{
stu.age = 100;
strcpy(stu.name,"New");
}
void f2( struct student* stu) //结构体指针作为形参
//传入的是结构体的地址,此时形参和实参指向同一个内存块
{
(*stu).age = 100;
strcpy((*stu).name,"New");
}
int main()
{
struct student s = {20, "Old"};
f1(s);
cout<<s.age<<"+"<<s.name<<endl; //在f1中进行修改,但是在main并不会变化
f2(&s);
cout<<s.age<<"+"<<s.name<<endl; //在f2中进行修改,在main中会发生变化
}
=====================================================
void f2(const struct student* stu) //此时用const修饰结构体指针变量,
//此时会导致编译失败:因为在f2函数体中对结构体的内容作了修改
{
(*stu).age = 100;
strcpy((*stu).name,"New");
}
=====================================================
下面介绍一个“奇怪的东东”:把同样的代码,放在C和C++程序中编译的结果竟然不一样!
代码如下:
#include "stdio.h"
int main()
{
const int a = 10; //a是一个只读的常量,按照理论a应该不能被修改
printf("修改之前 a = %d\n",a);
//a = 100; //注:此时编译失败,a不能被修改!疑问:那么a就不能被修改了么?
//答案:在C中,a被修改,但是在C++中a没被修改,具体情况见下:
int *p = NULL;
p = (int*)&a; //把a的地址赋给p :先取a的地址(&a是const int*类型),再把const int*转成int*类型,最后赋值给p
*p = 100; //用*p对a进行间接的修改,在C中修改成功,但是在C++中修改却失败
printf("修改之后 a = %d\n",a);
getchar();
}
执行结果竟然不一样:
在C中:
修改之前 a = 10
修改之后 a = 100 //发现,a竟然被修改
在C++中:
修改之前 a = 10
修改之后 a = 10 //发现,a竟然没被修改(这就是C++的牛逼之处,可以更好地使const修饰的变量不被修改)
【结论】在C语言中,const是一个冒牌货,const是一个只读变量,可以通过地址绕过const关键字并且对const修饰的变量进行间接修改;但是C++却怎么都绕不过const,const在C++中是一个常量,没法对const修饰的变量进行修改。
【疑问1:为什么发生上面的情况呢?】在C++中,通过const修饰的东西,会变成什么样子的呢?
当写完const int a = 10;时,会把上面的信息放在C++里面的符号表中,形如:
key = value
a 10
... ...
当你去用a的时候,C++编译器会从符号表里边拿数据,因此此处的a就是“名副其实”的“真正意义上”的常量。
【疑问2:&a能取到a的地址么?此时的*p又是多少?】
p = (int*)&a;
*p = 100;
答:当执行到p = (int*)&a;中的取地址&a时,此时C++编译器会给a变量分配一个内存,并且把a的值10放在该地址中,此时p的值确实等于a的地址。
在进行*p = 100;后*p的值就是100。
这样,C++编译器既做到了对C编译器的兼容,又做到了对const常量。
【小知识】#define与const的作用域不同
#include "stdio.h"
void f1()
{
#define a 10 //a在f2中可用
const int b = 10; //b在f2中不可用
}
void f2()
{
printf("a = %d",a);
//printf("b = %d",b); 编译失败
}
int main()
{
f1();
f2();
}
引用专题
【普通引用】
1、变量名的回顾
变量名实质上是一段连续存储空间的别名
程序中通过变量名来申请内存空间
通过变量名可以使用内存
问题1:对一段连续内存空间只能取一个别名么?答:不是,引出“引用”。
2、引用是C++中的概念,属于C++对C的扩展
引用能起到指针的作用
可读性更高
3、引用有内存空间么?
答:引用也有内存空间!
#include <iostream>
using namespace std;
struct student
{
char &a;
char &b;
};
int main()
{
cout<<sizeof(struct student)<<endl; //答案: 8
}
为什么结果是8?引用的本质是什么呢?
答:引用在C++中的内部实现是一个常量指针:type &name <-->type* const name。
C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的内存大小与指针相同。
【结论】引用在实现上,只不过是吧间接赋值成立的三个条件的后两步合二为一:当
实参穿给形参引用的时候,只不过C++编译器帮我们程序员手工取了一个实参地址,
传给形参引用(常量指针)
3、C++引用注意事项
当函数返回值为一个引用时,
若返回值为“栈变量”,则不能成为其它引用的初始值,不能作为左值使用。
若返回值为“静态变量”或“全局变量”,则可以成为其它引用的初始值,既可作为右值使用,也可以作为左值使用。