在C++中我们会自己定义很多的变量、函数和类,这就同时要取各种变量名、函数名和类名。如果这些变量、函数和类的名称都存在与全局作用域中,就要刻意避免重复命名造成的麻烦,要避免命名冲突或名字污染,就要对这些定义的数据类型进行严格管理,namespace就是用来对各种标识符名称进行本地化管理,避免上述麻烦的关键字。
语法格式是 namespace 命名空间名称 {} 。大括号里面就是用来定义变量、函数和类的,这样的话,即使在其他作用域下有同样的标识符名称也不会造成冲突,要用到这些名称时只要注明相应的命名空间即可,当然是全局作用域下的名称,就不用注明了,这就把我们要用到的各种标识符名称很好地管理起来了。具体看一下代码实现。
// 1.普通的命名空间方式
namespace N1
{
int add(int a, int b) // 这里在N1里面定义add函数
{
return a + b;
}
}
//2.命名空间可以嵌套
namespace N2
{
int a = 5;
namespace N3
{
int b=10;
}
}
//3.命名空间名称相同,最后编译器会将相同名称的命名空间合成一个
namspace N1
{
int sub(int a, int b)
{
return a-b;
}
}
//要用到命名空间中的内容,有以下3种方法
//1 命名空间名称加作用域限定符
int main()
{
std::cout << N2::a + N2::N3::b << std::endl; //cout 和endl就是C++的命名空间std中的对象名称
return 0;
}
//2.用using加命名空间加作用域限定符将所需名称引入
using std::cout;
using std::endl;
using N1::sub;
using N2::a;
using N2::N3::b;
int main()
{
cout << sub(a, b) << endl; //引入后的名称就可以直接使用
return 0;
}
//3.使用using namespace 命名空间名称 直接将整个命名空间展开
using namespace std;
using namespace N1;
using namespace N2;
using namespace N2::N3;
int main()
{
cout << add(a, b) << endl; //三种方式对比:第一种是每次使用名称都要加作用域限定符,第二种是
return 0; //引入的名称可以直接使用,第三种引入整个命名空间后该命名空间所有成员都可以直接使用
}
我们平常学习C++时为了方便地使用C++标准库中的函数或对象就常在代码前面加上using namespace std;
C++中的标准输入(键盘)和标准输出(控制台)所用的关键字分别是cin和cout,需要包含头文件iostream以及标准命名空间std,与C语言的scanf和printf有很大的不同,使用cout不用增加数据格式控制如%d,%f等,它会进行自动类型推导并输出,使用cin时同样不用加数据格式控制符,在键盘上输入当类型不同也会发生隐式类型转换。
int main()
{
int a;
double b;
cin >> a>>b;
cout << a <<" "<>配合使用 cout与<<配合使用 endl相当于换行符\n
}
C++函数的参数列表中可以有缺省参数,就是默认参数的意思,在调用函数传参时给了具体的值就用具体值,没有传实参拷贝值时就用默认的值。
//1.全缺省参数
void myprint1(int a = 2, int b = 5, int c = 10)
{
cout << a + b + c << endl;
}
//2.半缺省参数
void myprint2(int a, int b = 5, int c = 10)
{
cout << a + b + c << endl;
}
int main()
{
myprint1() ;
myprint1(10);
myprint2(5);
myprint2(3, 4, 5);
return 0;
}
4. C语言不支持这样的语法
C++中有函数重载的概念,简单地说就是允许定义函数名相同的函数,在调用函数时,根据函数传入参数类型不同、参数个数不同,参数顺序不同来区分是要调用同名函数中的哪一个函数。所用发生函数重载的情形相应的就有以下三种。
void func(int a, double b)
{
cout << "int和double" << endl;
}
void func(int a) //三个函数名都为func但两两比较有参数个数不同、参数类型不同、顺序不同
{
cout << "int" << endl;
}
void func(double a, int b)
{
cout << "double int" << endl;
}
int main()
{
func(1); //因此这里调用func会按照函数重载规则去调用相应的函数
func(1, 1.5);
func(2.5, 1);
return 0;
}
程序运行要经历预处理、编译、汇编和链接。当我们调用函数时在链接阶段,连接器就要按照函数名到相应的符号表中去找对应函数的入口地址去执行哪里的代码,C语言不支持函数重载就是因为符号表中的函数名就为函数定义时的函数名,而C++符号表中函数名有特殊的函数名修饰规则,在原函数名的基础上,编译器将函数参数类型信息添加到修改后的名字中,最后生成的符号表中的函数名是唯一对应一个函数入口地址的。这也解释了函数返回类型不是产生重载的因素。
引用可以通俗地理解给变量取别名,当我们定义一个变量时会在内存中相应地申请一块空间,我们为使用这块空间取了个名字,方便我们操纵这块空间的值,使用引用就为这块空间取了另外一个名字,使用另外的名字也可以来操纵这块空间的值。
引用的用法:类型 & 别名=引用实体
要注意引用类型必须和引用实体是同种类型的
//引用的特性
int main()
{
int a = 10,t=5;
int& b = a; //1.引用在定义时就要初始化
int& c = a; //2.一个变量可以有多个引用,即可以取多个别名
//int& c = t; //3.引用一旦引用一个实体,再不能引用其他实体
cout << &a << " " << &b << " " << &c << endl;
return 0;
}
int main()
{
//int& a = 10; //类型不同 a是变量,10是常量
const int& a = 10;
const int b = 5;
//int& c = b; //类型不同 从b到c权限放大,不允许这样,
int e = 8;
const int& h = e; //从e到h变量到常量是权限的缩小,循序这样
double d = 3.14;
//int& f = d; //类型不匹配
const int& f = d; //这里发生隐式类型转化d是double类型,产生临时拷贝值是int型具有常属性,f是临时拷贝值的引用
return 0;
}
1.作函数参数
int add(int& a,int& b)
{
return a+b;
}
2.作返回类型
int& add(int a,int b)
{
static int c=a+b;
return c;
}
注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
值传参和传值返回都是产生临时变量返回临时变量,中间有空间开辟和释放等操作,相比之下引用传参和返回就没有这些过程,所以使用引用可以提高运行效率。
用法就是在普通函数定义的开头加上修饰字inline,作用是编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,达到提升程序运行的效率。
inline int maxnum(int a,int b)
{
return (a>b)?a:b;
}
C++11开始auto是自动推导定义数据类型,用代码解释。
#include
int main()
{
int a=10;
auto b = a; //定义变量b就能推导它是int型,值是10
double c=3.14;
auto d = c; // 同样d就是double型,值3.14
cout << typeid(b).name() << endl;
cout << typeid(d).name() << endl;
cout << b << endl;
cout << d << endl;
vectorv1;
for (int i = 1; i < 5; i++)
{
v1.push_back(i); //上述用auto来推导int ,double等类型并没有体现auto带来的实质性好处
} //在stl中有很多类型名非常长,就能体现auto带来的便利了,这里v1.begin()返回的类型即变量
for (auto it = v1.begin(); it != v1.end(); it++)//it的类型是vector::iterator。auto it = v1.begin()和vector::iterator it = v1.begin()是等价的
{
cout << *it << " ";
}
return 0;
}
1.用auto来声明指针类型时,auto 和auto * 没有区别,但声明引用类型时一定要用 auto &
2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
int main()
{
int a = 10;
double b = 3.14;
//auto c = a, d = b; // 这里已经默认auto 推导的类型时int, 但后面b是double不能赋值给d
return 0;
}
注意auto不能用来作形参推导类型
void func(auto a) //这是不允许的
不能用来定义数组
auto arr[ ] ={3,5,7}; //不能这样定义数组
范围for循环的用法
int main()
{
int arr[] = { 1,2,3,4,5 };
for (auto e : arr) //必须传入数组名,自动依据数组元素个数推导循坏次数,单趟把数组的一个元素
{ //赋值给e,通过这种形式遍历数组。
cout << e << " ";
}
for (auto& e : arr) //加上引用符& 每趟循环生成不同的e来引用数组的元素从而操纵数组内容,这里就实现了数组各个元素+1的操作
{
e++;
}
cout << endl;
for (auto e : arr)
{
cout << e << " ";
}
return 0;
}
注意
void myprint(int arr[])
{
for (auto e : arr) //这里范围for的效果就会失效,应为arr一定要是数组名,而这里通过传参的方法时arr已经退化为指针了,范围不能确定了。
{
cout << e << " ";
}
}
在C++中关于null定义的代码如下
#ifndef NULL
#ifdef __cplusplus
#define NULL 0 //所以null在C++中是等同与整形数字0
#else
#define NULL ((void *)0)
#endif
#endif
void func(int) //
{
cout << "int" << endl;
}
void func(int*)
{
cout << "int*" << endl;
}
int main()
{
func(0);
func(NULL); // 我们本意是希望参数是空指针NULL走第二个函数但实际是走第一个函数
func((int*)NULL); //只用强制转换和参数是nullptr时才走第二个函数
func(nullptr);
return 0;
}
引入nullptr就是希望避免NULL是指针还是整形0的混淆引起的特定情形下的错误。
注意