命名空间
作用域的分类
全局作用域
局部作用域
类作用域
命名空间作用域
命名空间的定义和作用:
命名空间的主要作用是避免不同代码库或模块中的标识符发生冲突。通过将标识符放在不同的命名空间中,可以确保它们不会相互干扰,命名空间作用域指的是在命名空间中定义的标识符(如变量、函数、类等)可以被访问和使用的范围
命名空间的创建:
namespace k{
int a = 1;
namespace h {//嵌套创建命名空间
int b;
}
}
int main() {
printf("%d", k::a);//命名作用域内的参数可以被调用;
printf("%d",k::h::b);//调用嵌套命名作用域的参数(先在k中找到h再找到b)
}
命名空间的展开
那么什么是将namespace展开呢?这里就要提到using namespace ,已经创建好的一个命名作用域,如果使用using namespace...展开,命名空间域的内容(如变量,函数)就会被引入到全局作用域中,后续即便不使用"::"也可以引用里面的东西
using namespace k;
namespace k{
int a = 1;
namespace h {
int b;
}
}
int main() {
printf("%d", a);
printf("%d", h::b);}//被展开的是k,但是h没有被展开,而h现在位于全局作用域中,可以直接找到h,再用::找b
//或者
using namespace k;
using namespace h;
int main() {
printf("%d", b);//就可以直接调用b了
}
include的作用
其实include就是隐式地把一个头文件的内容拷贝到当前文件中,而这个头文件里面是有他自己的命空间的,如果使用using namespace std(大多数库函数都是用std作为命名作用域的名字,而重名的namespace会自动合并),这个头文件的命名作用域就会被引入到全局中,因此就不许要使用std::了;一般在文件开头会加一个using namespace std;
可以在.h文件中就展开成全局作用域,后面包含该头文件的都不用展开(图1)
或者在源文件中先包含头文件再展开,但每个包含头文件的都需要再展开(图2)
仅包含不展开会报错(图3)
调用变量时的匹配顺序
表达式中的变量将优先从当前局部域查找,再从全局中查找(包括展 开的命名空间),只有指定了命名空间才能找到命名空间域中的变量
#include <iostream>
using std::cout;
namespace k {
int a = 1;
namespace h {
int b;
}
}
int a=2;
int main() {
cout << a;//将输出2,同时如果展开k会报错(全局作用域将会出现两个a)
return 0;
}
项目中谨慎展开std
很多时候在项目中仍然会用”std::“来引用变量以防止后加入的头文件与自己之前写好的变量重名,或者指定某一个命名空间
#include <iostream>
using namespace k;
using namespace h;
using std::cout;//指定展开某个命名空间
int main() {
int a = 1;
cout << a;//可以使用cout了,而其他的就不行
return 0;
}
c++的输入和输出
cout是输出cin是输入,endls是换行符(或"/n"),这些包含在<iostream>头文件中,空格是 " ",
#include <iostream>
using namespace std;
int main() {
cout << "Hello world" << endl;
int b; cin >> b;
cout << b << endl;
return 0;
}
//输出
//Hello world
//b的值
cin\cout和printf、scanf的区别
-
cin和cout可以自动识别变量的类型,而printf和scanf需要手动输入
-
cout依据数据类型有自己的默认精度,如果要指定精度会比较麻烦,此时选择printf较优
#include <stdio.h> int main(void) { double x = 3.141592653589793; printf("%.2f\n", x); // 输出: 3.14 return 0; } #include <iostream> #include <iomanip> int main() { double x = 3.141592653589793; std::cout << std::fixed << std::setprecision(2) << x << '\n'; // 输出: 3.14 return 0; }
-
同步:cin和cout默认和C风格的输入输出流(如printf和scanf)保持同步。这是为了兼容C和C++代码混用的情况。同步机制会使得每次进行输入输出操作时,都要进行额外的检查和同步操作,这增加了开销。而printf和scanf没有这种同步机制,它们直接操作底层的输入输出缓冲区,效率更高。开头写上std::ios_base::sync_with_stdio(false)以取消同步流从而增加效率
缺省参数
缺省参数在函数声明或定义中是从右往左依次设立的,因为函数调用时不能跳过某个参数使其对应缺省参数(也就是说函数调用时必须从左往右赋值,剩下的才能自动对应缺省参数)
void test(int a=1, int b=2, int c = 3) {
cout << a << b << c;
}
//错误案例:
void test(int a =1, int b , int c = 3) {
cout << a << b << c;
}//报错:默认实参不在形参列表的结尾
void test(int a = 1, int b = 2, int c ) {
cout << a << b << c;
}//报错:默认实参不在形参列表的结尾
int main() {
test(3, 3);//输出3 3 3
test(3,,3);//报错
}
另外声明和定义同时存在时,只能在声明中设置缺省参数
报错重定义,写成下面这样就好了
函数的重载
c++中允许两个同名的函数存在,通过区分它们的参数的顺序,参数的个数,以及参数的类型(注意不是参数的名字以及返回值的类型)从而来确定要调用哪一个函数
void ret(int a, double b) {
cout << a << endl;
}
void ret(double b, int a) {
cout << b << endl;
}
void ret(int a){
cout<<a<<endl;
}
int main() {
ret(1, 1.1);//输出1
ret(1.1, 1);//输出1.1
ret(2);//输出2
}
不构成重载的情况以及错误的情况
void f(int a, double b) {
cout << "a" << endl;
}
void f(int b, double a) {
cout << "a" << endl;
}//本质上是一致的
void ret() {
cout << "a" << endl;
}
void ret(int a=2) {
cout << "a" << endl;//会产生歧义
}
int main(){
ret();//当什么都不调用时,既满足触发缺省参数的条件又满足没有参数的条件,此时会产生歧义
}
函数重载只依赖参数(类型、个数、顺序)而不依赖返回值,根本原因在于编译器在调用点只能看到调用语句本身,而调用语句里没有返回值信息
引用
定义和应用
变量的别名,在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间,在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main() {
int b = 2;
int& a = b;
int& c = a;//引用的引用
a += 3;
cout << b<<endl;//5
c += 2;
cout << b<<endl;//7
cout << a<<endl;//7
return 0;
}
引用作为参数
void ADD(int& a, int& b) {
a += b;
return;
}
int main() {
int a = 2;
int b = 3;
ADD(a,b);
cout << a << endl;//5
return 0;
}
int& ADD() {
static int a = 3;//必须用一块额外的空间存储将要返回的值
a += 4;
return a;
}
int& ADD() {
int a = 4;//如果写成这样,在返回时就会销毁a所处的空间,返回值引用的是a的空间就会引发错误
a += 3;
return a;
}
int main() {
cout << ADD() << endl;//7
return 0;
}
传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
如果是传引用的话,不会有拷贝的过程,效率高
引用和指针的不同点
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用(引用的引用不是多级引用)
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
内联函数
在C语言学习时有宏函数,有它自己特殊的的写法和注意事项,从而实现不另外开空间的函数调用,而C++中在函数定义前增加inline即可将函数展开为函数体,此时该函数也就具有了宏函数的优缺点
内联函数的优缺点
优点:
1.增强代码的复用性
2.提高性能
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查
内联函数注意事项
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用
2. inline对于编译器而言只是一个建议,一般建将函数规模较小、不 是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
3.内联函数的声明和定义不能分开,不然会发生链接错误
auto关键字
int main() {
int a = 3;
auto c = a;
cout << c << endl;//3
return 0;
}
int main() {
int a = 0;
auto b = &a;
auto* c = &a;//用auto声明指针类型时,用auto和auto*没有任何区别
auto& d = a;//用auto声明引用类型时则必须加&
return 0;
}
1.使用auto定义变量时必须对其进行初始化
2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译
器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
3.auto不能作为函数的参数
4.auto不能直接用来声明数组
范围for
struct k {
int data;
double data2;
};
int main() {
int a[] = { 1,2,3,4,5 };
for (int i = 0; i < 5; i++) {
cout << a[i] << endl;
}///这是一般的遍历
for (auto temp : a) {
cout << temp << endl;
}//范围for,结构是定义变量 : 数组,自动将数组的值赋值给变量,并自动迭代自动判断结束;
k b[] = { {1,1.1},{2,2.2},{3,3.3} };
for (auto temp : b) {
cout << temp.data << endl;//在这里temp是结构体类型
}
return 0;
}
指针空值nullptr
在C++中NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量,因此要表示空指针最好使用nullptr
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。