目录
1.4 同一个工程允许多个相同名称的命名空间,编译器最后会将其合成同一个命名空间
1.5.2 使用using namespace命名空间名称引入
在 C/C++ 中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是 对标识符的名称进行本地化 ,以 避免命名冲突或名字 污染 , namespace 关键字的出现就是针对这种问题的。
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名 空间的成员。
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
全部展开,用起来方便了,隔离就失效了,所以应该谨慎使用
- //单独展开某一项,用于展开命名空间中常用的
- using N1::N2::Node;
- int main()
- {
- struct Node node;
- }
说明:1. 使用 cout 标准输出 ( 控制台 ) 和 cin 标准输入 ( 键盘 ) 时,必须 包含 < iostream > 头文件 以及 std 标准命名空间。注意:早期标准库将所有功能在全局域中实现,声明在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在 std 命名空间下,为了和 C 头文件区分,也为了正确使用命名空间,规定 C++ 头文 件不带 .h ;旧编译器 (vc 6.0) 中还支持 <iostream.h> 格式,后续编译器已不支持,因此 推荐 使用 <iostream>+std 的方式。2. 使用 C++ 输入输出更方便,不需增加数据格式控制,比如:整形 --%d ,字符 --%c
上图可以看出,如果只输出数值的话,还是C++更方便一些,C语言保留了6位小数。而如果结构体student中的成员越多的话,还是C语言的输出语句更方便一些,所以C语言和C++哪个更方便就可以选用哪个。
缺省参数是 声明或定义函数时 为函数的 参数指定一个默认值 。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
注意:1. 半缺省参数必须 从右往左依次 来给出,不能间隔着给2. 缺省参数不能在函数声明和定义中同时出现
//a.h void TestFunc(int a = 10); // a.c void TestFunc(int a = 20) {} // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那 个缺省值。3. 缺省值必须是常量或者全局变量4. C 语言不支持(编译器不支持)
函数重载 : 是函数的一种特殊情况, C++ 允许在 同一作用域中 声明几个功能类似 的同名函数 ,这些同名函数的形参列表 ( 参数个数 或 类型 或 顺序 ) 必须不同 ,常用来处理实现功能类似数据类型不同的问题
引用 不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如: 李逵 ,在家称为 " 铁牛 " ,江湖上人称 " 黑旋风。
类型& 引用变量名(对象名) = 引用实体
引用在语法层上来讲是没有开辟新的空间的,就是对原来的空间取了一个新名称叫做b
注意:引用类型必须和引用实体是同种类型的
- //传指针
- void Swap(int* p1,int* p2)
- {
- int temp = *p1;
- *p1 = *p2;
- *p2 = temp;
- }
- //传引用,引用做参数,把a,b起个别名
- void Swap(int& r1,int& r2)
- {
- int temp = r1;
- r1 = r2;
- r2 = temp;
- }
- //传值
- void Swap(int r1, int r2)
- {
- int temp = r1;
- r1 = r2;
- r2 = temp;
- }
- //他们三个构成重载(类型不同)
- //但是Swap(a,b);调用是存在歧义,他们不知道调用,传值还是传引用
-
- int main()
- {
- int a = 10, b = 20;
- Swap(&a,&b);
- /*Swap(a,b);*/
- }
注意: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统(全局变量、静态区),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回,不能使用传引用返回.
- //常引用
- int main()
- {
- //权限放大: a是只读的,而b是a的引用,竟然是可读可写的,那是不可以的
- const int a = 10;
- int& b = a;
-
- //权限不变: c和d都是只读的 ,可以
- const int c = 10;
- const int& d = c;
-
- //权限缩小:f是只读的,可以
- int e = 10;
- const int& f = e;
- }
1. 引用 在定义时 必须初始化 ,指针没有要求2. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型实体3. 没有 NULL 引用 ,但有 NULL 指针4. 在 sizeof 中含义不同 : 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占4 个字节 )5. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小6. 有多级指针,但是没有多级引用7. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理8. 引用比指针使用起来相对更安全9.引用概念上定义一个变量的别名(没开辟空间),指针存储一个变量的地址
有时候在 C++ 工程中可能需要 将某些函数按照 C 的风格来编译 , 在函数前加 extern "C",意思是告诉编译器, 将该函数按照 C 语言规则来编译。
1.包含头文件
2.在工程属性中配置静态库的目录和添加静态库注意:可以理解为,只有c++认识extern C
总结:
C++程序调用C的库,在C++程序中加extern"C"
C程序调用C++的库,在C++库中加extern"C"
因为调用函数,需要建立栈帧,栈帧中要保存一些寄存器,结束后又要恢复,频繁的调用函数,这样的操作是有消耗的。而C和C++都有应对的措施。
C语言通过宏(一种替换,不需要建立栈帧)
#define Add(x,y) ((x)+(y)) //最好把((x)+(y))中x和y括号都加上,否则比如(x+y),10*Add(3,4),会替换为10 * 3+4,会导致出错 int main() { cout << Add(1, 2) << endl; return 0; }
C++ 以 inline 修饰 的函数叫做内联函数, 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数压栈的开销, 内联函数提升程序运行的效率。
查看方式:
1. 在 release 模式下,查看编译器生成的汇编代码中是否存在 call Add2. 在 debug 模式下,需要对编译器进行设置,否则不会展开 ( 因为 debug 模式下,编译器默认不会对代码进行优化,以下给出 vs2013 的设置方式 )
1. inline 是一种 以空间换时间 的做法,省去调用函数额开销。所以 代码很长 或者有 循环 / 递归 的函数不适宜使用作为内联函数。2. inline 对于编译器而言只是一个建议 ,编译器会自动优化,如果定义为 inline 的函数体内有循环 / 递归等 等,编译器优化时会忽略掉内联。3. inline 不建议声明和定义分离(直接再定义前面加inline),分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。
在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量 ,但遗憾的是一直没有人去使用它,大家可思考下为什么?C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得 。
- int main()
- {
- const int a = 0;
- int b = 0;
- //自动将a的类型推到c,
- auto f = a;//int
- auto c = &a;//int const*
- auto d = 'A';//char
- auto e = 10.11;//double
- //f,d,e的类型是const的,具有常性,但是auto会忽略这个const
-
- //typeid打印变量的类型
- cout << typeid(f).name() << endl;
- cout << typeid(c).name() << endl;
- cout << typeid(d).name() << endl;
- cout << typeid(e).name() << endl;
-
- auto e;//err,auto定义变量时必须对其进行初始化
- }
[注意]使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类型 。因此 auto 并非是一种 “ 类型 ” 的声明,而是一个类型声明时的 “ 占位符 ” ,编译器在编译期会将 auto 替换为 变量实际的类型 。
用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto声明引用类型时则必须加&
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量 。
- void TestAuto()
- {
- auto a = 1, b = 2;
- auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
- }
在C/C++中遍历数组可以有如下的方式
- //语法糖
- int array[] = {1,2,3,4,5};
- //c/c++遍历数组
- for (int i = 0; i < sizeof(array)/sizeof(int);i++)
- {
- cout << array[i] << endl;
- }
对于一个 有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11 中引入了基于范围的 for 循环。 for 循环后的括号由冒号 “ : ” 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围 。
- //C++11 范围for
- //自动依次取数组array中的每个元素赋值给e
- for (auto e:array)
- {
- cout << e << endl;
- }
注意:与普通循环类似,可以用 continue 来结束本次循环,也可以用 break 来跳出整个循环 。
那如何运用范围for将数组中的每个值+1呢?
- for (auto e : array)
- {
- e++;//此时是array数组将每个值赋值给e,e的改变并不会影响到array数组中的值
- }
这个方法是不行的,因为此时是array数组将每个值赋值给e,e的改变并不会影响到array数组中的值,那怎样才能改变呢?可以用引用的方法,数组中的每个值赋值给e变量,而e变量又是该值的别名,修改的是同一份空间,所以会改变数组的值,而应该注意的是每次的e的赋值的作用域只在一个循环之中。这种方法是可以解决这个问题的,代码如下:
- for (auto& e : array)
- {
- e++;//此时是array数组将每个值赋值给e,e的改变并不会影响到array数组中的值
- }
for 循环迭代的范围必须是确定的 :对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供 begin 和 end 的方法, begin 和 end 就是 for 循环迭代的范围。
- void TestFor(int array[])
- {
- for (auto& e : array)
- cout << e << endl;
- }
-
- int main()
- {
- int array[] = { 1,2,3,4,5 };
- TestFor(array);
- }
以上的代码是有问题的,for的范围是不确定的,因为范围for的范围必须是数组名,而数组传参,传过去之后会退化为指针,所以其实TestFor的array是一个指针,所以是有问题的,应当注意
NULL其实是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
- #ifndef NULL
- #ifdef __cplusplus
- #define NULL 0
- #else
- #define NULL ((void *)0)
- #endif
- #endif
此时NULL可能被定义为常量,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。问题如下:
- void f(int)
- {
- cout << "f(int)" << endl;
- }
- void f(int*)
- {
- cout << "f(int*)" << endl;
- }
- int main()
- {
- f(0);
- f(NULL);
- f((int*)NULL);
- return 0;
- }
f(NULL)和f(0)对应的都是f(int),而程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。而在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0, 所以在C++11中,引入了新关键字nullptr。
注意:
1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr 是 C++11 作为新关键字引入的 。2. 在 C++11 中, sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr 。