对于类、函数或变量的在同一作用域中时,可能会因为相同标识符冲突。因此使用命名空间的目的是:对标识符的名称进行本地化,避免命名冲突或名字污染。
定义一个命名空间,就定义了一个作用域,命名空间中的内容都局限于该命名空间
使用关键字:namespace
//1.普通命名空间
namespace N1//N1是命名空间的名称
{static int times = 0;int add(int n1, int n2){return n1 + n2;}
}//2.嵌套的命名空间
namespace N2
{int sub(int n1, int n2){return n1 - n2;}namespace N3{int sub(int n1, int n2){return abs(n1 -n2);}}
}//3. 同名命名空间
//同一个项目中的多个同名命名空间,在编译的时候
//会合并为同一个命名空间
namespace N1
{int sub(int n1, int n2){return n1 - n2;}
}
以上述的三个命名空间举例,命名空间的使用主要有三种方式:
//1.使用其中某个函数、变量或类时,
//前面加 命名空间名 和 作用域限定符
int main()
{printf("%d\n", N1::times);int ret1 = N2::sub(1, 9);int ret2 = N2::N3::sub(1, 9);return 0;
}
//2. 使用using引入命名空间中成员
using N1::times;//sub函数会发生命名冲突
//using N2::sub;
//using N2::N3::sub;int main()
{printf("%d\n",times);//int ret1 = sub(1, 9);//int ret2 = sub(1, 9);return 0;
}
//3. using namespace + 命名空间
using namespace N1;
int main()
{printf("%d\n",times);int ret1 = add(1, 1);int ret2 = sub(1, 1);return 0;
}
对于方法3,因为会将命名空间的内容全部展开(就相当于放在全局域中),就无法起到它的作用了。因此,在实际项目中建议使用方法2展开常用的,能更好的避免命名冲突。
#include int main()
{int a;double b;char c;std::cin >> a >> b >> c;std::cout << "---------\n";std::cout << " a = " << a;std::cout << " b = " << b;std::cout << " c = " << c << std::endl;return 0;
}
其中cin
不会记录空格和换行符,cout
会自动识别类型,endl
的作用相当于\n
在声明或定义函数时,为其参数指定默认值。则可以在没有实参的情况下,使用该的默认值。
int add(int a = 0, int b = 0)
{return a + b;
}
int main()
{add(); //没有传参,会使用默认值add(1, 1);//传入实参 1,1return 0;
}
int add(int a = 0, int b = 0, int c = 0)
{return a + b + c;
}
int main()
{add(); // a=0,b=0,c=0add(1); // a=1,b=0,c=0add(1, 2); //a=1,b=2,c=0add(1, 2, 3);//a=1,b=2,c=3return 0;
}
int add(int a , int b = 0, int c = 0)
{return a + b + c;
}
int main()
{//add();// a不是缺省参数所有必须传参add(1); // a=1,b=0,c=0add(1, 2); //a=1,b=2,c=0add(1, 2, 3);//a=1,b=2,c=3return 0;
}
函数的参数列表中,半缺省参数必须从右→左依次定义没有默认值的参数,不能间隔
函数的定义和声明不能同时出现缺省参数
同时使用默认参数时,如果给定的默认值不同也会造成冲突。
因此只用在声明或定义时使用缺省参数。
int add(int a , int b = 0, int c = 0);int add(int a, int b, int c )
{return a + b + c;
}
缺省值必须是常量或者全局变量
c++允许在同一作用域下,有多个参数列表不同,函数名相同的函数,称为函数重载,用于处理数据类型不同但是功能相似的问题。
#include using namespace std;int Add(int n1, int n2)
{return n1 + n2;
}
double Add(double n1, double n2)
{return n1 + n2;
}int main()
{int a = 1;double b = 1;cout << Add(a, a) << endl;cout << Add(b, b) << endl;return 0;
}
调用函数时,会根据参数类型自动匹配到对应的函数。
c语言不能支持函数重载,如果想要实现上述函数,只能命名如Add1(),Add2()
,来区分不同函数。
那么为什么C++可以支持函数重载呢?
从写好的
.cpp
———>可以运行的.exe
文件,需要经历预处理,编译,汇编,链接几个过程。在链接阶段,链接器会将调用的Add的地址找到,然后链接起来,其中链接器也是通过名字去找到的,而且不同编译器对函数名都有自己的修饰规则。
因为Linux下gcc/g++对函数的命名规则比较简单,以下示例:
test.c
文件,经过c语言编译器gcc
编译后,我们可以发现其汇编语言中,调用的分别对应Add()——< Add >,Sub()——< Sub >
test.cpp
文件,经过c++编译器g++
编译后,其汇编语言中g++将函数名修饰后变成**{_Z+函数名长度+函数名+参数类型首字母}**
由此可以看出,是因为c++的编译器会对函数名进行修饰,因此可以支持函数重载。
值得一提的是,返回值不同不是函数重载的要素。
由于c和c++编译器对函数名的修饰规则不同,因此c++中调用c语言实现的静态库或者动态库时会出现问题。因此就需要在函数前加extern “C”,来告诉编译器,将该函数按照c语言的规则来编译。
参考链接:创建与使用静态库
在c++程序中,包含Add.h
后,经过extern "C"修饰,就可以成功使用c语言的静态库了。
语法:类型& 引用名 = 引用实体;
相当于给一个已经存在的变量,起一个别名,可以通过这个引用名访问到该变量本身
const
修饰的变量,需要使用const
修饰的引用,即为常引用常量
a
需要常引用c
来引用
d
作为int型的常引用来引用double型的变量b
,是因为常引用d
实际上引用的是b
转换为int型的拷贝(地址不同)
void Swap(int& a, int& b)
{int temp = a;a = b;b = temp;
}
a,b
都是引用类型,因此在Swap
内的交换可以改变其引用的实体
适用场景:输出行参数(如上例),大对象传参
int& Add(int a, int b)
{static int sum = a+b;return sum;
}
由于sum
是静态变量,因此其作用域为全局,可以返回其引用。
适用场景:修改返回对象,大对象返回
下例为一个错误示范 :
由于调用完Add()
函数后不久,系统会回收其所占用的栈空间,其中就包括sum
,因此对于a
最后会变成随机值。
在语法概念上引用就是一个别名,和其引用实体共用一块空间;指针有固定大小的空间(4/8),存放的是所指向空间的首地址
在底层实现中,引用实际有自己的空间,因为引用是按照指针的方式来实现的。
如图,指针和引用的操作,其汇编代码相同
不同点:
1.引用在定义时必须初始化,指针无此要求
2.引用不能再更改为其它引用实体,指针可以指向任意同类型实体
3.没有
NULL
的引用,有NULL
的指针4.在
sizeof
下含义不同,引用结果为引用类型的大小,指针结果为自身(地址空间,32位平台下占4个字节)的大小5.引用
++
即实体增1,指针++
即指针指向向后偏移一个类型的大小6.没有多级引用,有多级指针
7.访问实体时,引用时编译器会自动处理,指针需要显示解引用
8.引用比指针使用更安全
以inline
修饰的函数叫做内联函数。编译时,编译器会在调用内联函数的地方将函数展开,来减少函数调用所建立和销毁栈帧的开销,提高程序的运行效率。
当使用inline
后,可以看见没有调用函数call
了,而是直接执行函数的语句。
c语言中,对于类似问题的解决,提供宏函数来提高效率
#define Add(a+b) ((a)+(b))
宏的优点:1.复用性强 2.提高效率
宏的缺点:1.可读性差 2.没有类型安全检查 3.不方便调试(因为预处理阶段进行了替换)
C++中基本不再建议使用宏,尽量使用const、enum、inline来代替宏
inline
对于编译器而言只是一个建议,编译器会自动优化。一般将函数规模较小、不是递归、频繁调用的函数,采用inline修饰,否则会忽略inline特性。auto
的作用是自动推导类型,用于解决类型难拼写、类型不明确
使用
auto
定义变量时,必须初始化。编译时编译器根据初始化表达式来推导auto的实际类型,将auto替换为变量实际的类型。因此auto是声明类型时的"占位符"。
指针与引用
用auto声明指针类型时,使用auto
和auto*
没有区别,但是声明引用类型 时必须加&
同一行定义多个变量
当在一行声明多个变量时,这些变量必须是相同的类型。因为编译器实际只对第一个类型进行推导,然后根据推导出来的类型定义其他的变量。
auto a = 1, b = 2, c = 3;
auto不能作为函数的参数
编译阶段无法推导实际类型
auto不能直接声明数组
对于有范围的集合的整体遍历,可以不用像for(;;)
来说明循环的范围。
int a[] = { 1,2,3,4,5 };
for (int t : a)
{cout << t << " ";
}
for()
内用:
来分隔成两部分,int t
表示用于迭代的变量,a
表示被迭代的范围。循环从开始到结束,会依次取数组a的值赋值给局部变量t,然后输出。
int a[] = { 1,2,3,4,5 };
for (auto t : a)
{cout << t << " ";
}
当不清楚集合a的类型时,可以直接使用auto来推导
int a[] = { 1,2,3,4,5 };
for (auto& t : a)
{t++;cout << t << " ";
}
使用引用后,t就是a元素的别名,因此可以通过t来改变a中元素的内容
continue
,break
。//void test(int a[])
//{
// for (auot& t : a)
// {
// cout << t << " ";
// }
//}
对于上例中,函数参数a接收的是一个指针,因此数组的范围不能明确,会报错。
在c语言里,我们使用的是NULL来表示空指针。NULL实际上是一个宏,底层实现下:
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif
因此NULL可能被定义为字面常量0或者是无类型指针(void*)的常量,而字面常量0,编译器默认看作为整型常量。
因此会出现下例问题:
语法中NULL应该表示空值指针,程序却没有调用
fun(int* a)
,相悖 。
建议使用nullptr来表示空指针。
🦀🦀观看~