一、命名空间
语法:
namespace + 命名空间的名字{ 该命名空间里面的内容,可以是变量/函数/类型}
namespace young { //命名空间中可以定义变量/函数/类型 }//一个名为young的命名空间
作用:
对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题。(防止命名冲突)
#include<iostream> namespace young { int num = 4; int Add(int a, int b) { return a + b; } } int num = 5; int Add(int a, int b) { return a + b; } int main() { //int num = 4; printf("%d\n", num); //当我们要调用的是全局的num时这里只能将main这里的num注释。 //这时我们就可以用命名空间解决这个问题 printf("%d",young::num) //这里调用的就是命名空间young 的num 解决了作用域不同命名冲突的问题 printf("%d\n", young::Add(1, 2)); printf("%d\n", Add(3, 4)); }
结果为5 4 3 7。
C++中的域作用限定符是一种用于指定命名空间或类的成员的方法。它们使用双冒号运算符(::)来指定作用域。
如上述代码young::num
这将告诉编译器要在"young"命名空间中查找"num"这个变量。
::叫做域作用限定符,命名空间名字+::就可以访问命名空间里面的成员。
使用:对应三种方法
使用命名空间的三种方法:
1.通过域作用限定符使用命名空间。
2.将命名空间全部展开。//所有的命名空间里面的成员都可以进行使用
3.将命名空间部分展开。//只有展开的可以使用
#include <iostream> namespace y2 { double num = 3.15; int Add(int a, int b) { return (a + b); } } namespace y3 { char ch = 'A'; int sub(int a,int b) { return (a - b); } } namespace y4 { int num = 20; } using namespace y2;//2.将命名空间全部展开 using y3::ch;//3.将命名空间部分展开 int main() { printf("%d\n",y4::num);//1.::域作用限定符的方法 printf("%lf\n", num); printf("%d\n", Add(1, 2));//y2 全部展开Add函数也可以用 printf("%c\n", ch); //printf("%d\n", sub(3, 2)); y3 只展开了ch这个变量没有展开 sub这个函数 return 0; }
为啥名为 y1的命名空间?因为y1是一个函数……
y1是Bessel函数的一种,它是第二类贝塞尔函数的导数。在C++中,可以使用<cmath>库中的函数来计算Bessel函数,其中y1函数的原型如:double y1(double x);
它的作用是计算x的第二类贝塞尔函数的导数。需要注意的是,y1函数的参数x必须是非负实数。
额外补充
命名空间可以嵌套,同名的命名空间会自动合并,但里面的不能有同名的变量。
//命名空间的嵌套与命名空间的合并 #include<iostream> namespace y //3层命名空间的嵌套 { int a = 10; namespace y2 { int b = 20; namespace y3 { int c = 30; } } } namespace y { int a1 = 15; namespace y2 { double s = 3.14; } } namespace y2 { char ch = 'A'; } int main() { printf("%d,%d\n",y::a,y::a1);//同名的合并,第一层 printf("\t%d\n",y::y2::b); //第二层 printf("\t%lf\n", y::y2::s); //同层的同名才会合并 printf("%c\n",y2::ch); printf("\t\t%d\n",y::y2::y3::c);//第三层 return 0; }
同层的同名的命名空间才会合并。
在工程项目中不要轻易展开std,容易产生冲突。但是在日常练习中,为了方便可以展开。
关于 std 这个命名空间
std是一个命名空间,C++标准库中的标准函数与标准类都在std中定义。
关于头文件iostream,io流,输入输出流
关于cout,cin,endl, <<流插入运算符,>>流提取运算符。
cout——c,console(控制台),out 。 整个 cout<<"hello";就是将hello这个字符串插入到控制台中然后out显示出来
cin——c,console(控制台),in。整个 cin >> a;从控制台输入一个值提取到a这个变量中
这里还涉及到的是c++的运算符重载。
C++完全兼容c
二、C++的输入输出
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream >头文件中。3. <<是流插入运算符,>>是流提取运算符。4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。后面我们还有有一个章节更深入的学习IO流用法及原理。注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
三、缺省参数(默认参数)
缺省参数是C++中引入的一个概念,可以让函数自带默认值。
作用:使函数带有默认值,更加灵活。
#include<iostream> using namespace std; int Add(int a = 10, int b = 20) { return (a + b); } int main() { cout << Add() << endl; //不传参的话用函数自定义的默认值 cout << Add(1, 2) << endl; //传参使用所传的值 return 0; }
全缺省
使函数的参数都带有默认值,上述的代码中的Add函数就是全缺省。
半缺省
使函数的参数部分带默认值。
这个默认值必须从函数参数的右往左给(后往前),如果最左边的缺省参数右默认值,那么后面的参数都要有默认值数。半缺省参数必须从右往左依次来给出,不能间隔着给
#include<iostream> using namespace std; //半缺省 int Add(int a, int b = 20, int c = 30) { return (a + b + c); } //不能这样给缺省参数默认值 /*int Add(int a, int b = 20, int c) { //或a = 10,后面b,c都没有默认值,这样也是不行的。 return (a + b + c); }*/ int main() { cout << Add(1) << endl;//只传一个值,匹配的是从左到右 cout << Add(1, 2) << endl;//实参传给形参是从左到右进行匹配 cout << Add(1, 2, 3) << endl; //报错cout << Add(, 1, );那我只传中间的可以吗? 不行只能从左往右传,参数从左往右进行匹配 return 0; }
特性
缺省参数的默认值只能从右往左给,半缺省参数必须从右往左依次来给出,不能间隔着给
实参传给形参是从左到右进行匹配,如果前面还有参数,不能传给后面。只能从前到后匹配
如果所传的参数个数的不满足函数接收的个数,默认从左往右匹配。
缺省参数不能在函数声明和定义中同时出现
//缺省参数不能在函数声明和定义中同时出现 #include<iostream> using namespace std; int func(int a = 10); //会报错“func”: 重定义默认参数 : 参数 1 int func(int a = 5) { return a; } int main() { printf("%d\n",func()); return 0; }
四、函数重载
说明:
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。简单理解为一词多义。
一个简单的例子:
在课间的时候学习委员说下节课就要交作业了,你的学霸同桌早早就写完了,而你去外面耍把作业都给忘了,你的前桌同学问你们两个作业写完了,你的学霸说“完了”,你也说了“完了”。这时这个“完了”就构成了一词多义。
条件
同一作用域下
函数名相同,参数不同。
参数不同细分
参数的类型不同,参数的个数不同,参数的顺序(数据类型的顺序而不是参数的顺序)不同
代码展示
//函数重载 #include<iostream> using namespace std; int Add(int a, int b) { return a + b; } int Add(int a, int b, int c)//函数名相同,参数的个数不同 { return a + b + c; } double Add(double a, double b)//函数名相同,参数的类型不同 { return a + b; } void Func(int a, double b) { cout << "Func(int a, double b)" << endl; } void Func(double a,int b)//函数名相同,参数的类型顺序不同 { cout << "Func(double a,int b)" << endl; } int main() { cout << Add(1, 2) << endl; cout << Add(1, 2, 3) << endl; cout << Add(1.2,2.2) << endl; Func(1, 2.2); Func(2.2, 1); return 0; }
函数重载的原理:
真正的全部理解起来,我感觉很难,理解得不太来。
c编译的是后就进行报错 ,c将同名函数放到符号表里区分不开
如何将两个函数的符号表进行区分呢?c++使用函数名修饰规则可以实现。
函数名修饰规则,将函数的参数类型也带进去进行编译链接。
而c只带了函数名去,同名的话就区分不开。
五、引用&
引用最简单的理解就是取别名,这个别名与原来的变量共用一块空间。别名就是取外号。如张三可能会被人叫老张也可能被人叫小三,但指的都是同一个人。
#include<iostream> using namespace std; void swap(int& x, int&y)//x作为a的别名,y作为b的别名 { int temp = x; x = y; y = temp; } int main() { int a = 1; int b = 2; swap(a, b);//本来值传递形参是不能改变实参的,但形参a引用了实参a两者共用一块空间 cout << a <<" "<< b << endl; return 0; }
a 与 x 的地址是一样的,b 与 y的地址是一样的。
特性
1.引用必须在定义的时候初始化,让他知道是谁的别名
2.一个变量可以有多个引用 引用可以套引用
3.引用一旦引用了一个实体,再不能引用其他实体 引用只能引用一个,后面不能更改引用的指向。
//引用的特性 #include<iostream> using namespace std; int main() { int a = 10; //int& n;//1.引用要在定义时初始化 int& b = a; int& c = a;//2.一个变量可以有多个引用b,c都引用了a int& d = b;//2.引用可以套引用,c引用了b,b引用了a int x = 20; b = x;//3.只是将x的值赋了b 而不是改变了b的指向 //cout << a << endl; return 0; }
图片结合上述代码了解引用特性
引用做返回值
//引用做返回值 #include<iostream> using namespace std; int& Add(int a, int b) { int c = a + b; return c; } int main() { int ret = Add(1, 2); cout << ret << endl;//可能是原本的值也可能是随机值 return 0; }
传引用返回的话,就是函数中那块空间的别名,如果这块空间在传回来之前,被销毁了或者用于其他做其他的内存空间了就可能是随机值。
使用条件:
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。引用的权限
在引用的过程中 权限可以缩小 可以平移 但是不可以放大//引用的权限 #include <iostream> using namespace std; int main() { int a = 10; const int& b = a;//1.权限的缩小。将变量->常量 const int& c = b;//2.权限的平移。都是常量 //int& d = c;3.不能放大,不能将常量->变量 return 0; }
引用与指针
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
引用和指针的不同点:1. 引用概念上定义一个变量的别名,指针存储一个变量地址。2. 引用在定义时必须初始化,指针没有要求3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体4. 没有NULL引用,但有NULL指针5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小7. 有多级指针,但是没有多级引用8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理9. 引用比指针使用起来相对更安全
六、内联函数
内联函数不会建立栈帧不生成指令在调用的地方展开不会进入符号表。
vs开启内联函数拓展
先打开内联函数的扩展,才能使用到内联函数
特性
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。一般来说,内联只适用于规模较小、流程简单、调用频繁的函数。但函数里面的内容体量过大是函数也不会支持内联。代码展示
#include<iostream> using namespace std; int Sub(int a, int b) { int c = a - b; return c; } inline int Add(int a, int b)//内联函数 { int c = a + b; return c; } int main() { int ret_add = 0; int ret_sub = 0; ret_add = Add(1, 2); ret_sub = Sub(2, 1); cout << ret_add << endl; cout << ret_sub << endl; return 0; }
在函数定义前面+inline,该函数就变成内联函数。
以上为汇编代码解释过程,call 表示调用了函数。
从上图我们可以知道使用了内联的Add函数是没有被调用的,他是直接展开而没有使用内联的Sub函数是被调用了的。使用内联函数,当我们使用编译器进行调试时,内联函数是不会被调用的,光标不会进入内联函数的函数体里面。
当内联函数声明和定义分离时为什么间接调用又可以使用内联函数?
内联函数在符号表里面没有地址(在用的时候就展开了,没有必要生成指令放到符号表中),在间接时会展开。内联函数与宏的
宏的缺点: 不能调试,没有类型安全的检查
宏的优点:一定程度兼容不同的类型,不需要建立栈帧提高了效率C++有哪些技术替代宏?1. 常量定义 换用const enum2. 短小函数定义 换用内联函数
七、auto自动识别类型(c++11)
用法
//auto自动识别类型 #include<iostream> using namespace std; void test_for() { int array[] = { 1,5,8,7,6,9,3,2,4 }; for (auto x: array) { cout << x << " "; } } int main() { int a=0; auto b = a; //根据左边的类型进行自动识别 auto c = 1; //c为int 类型 /*auto b;*/ //auto 必须初始化才能使用 cout << typeid(c).name() << endl;//typeid(变量名字).name()可以查看变量的类型 test_for();//基于auto的for循环 return 0; }
1.auto必须初始化才能使用
2.auto会根据左边的类型进行自动识别从而给自己所定义的变量
3.auto一般用于简化代码,到后面学校STL的时候定义迭代器我们就可以使用auto自动识别类型进行简化。
4.auto不能定义数组,不能做返回值,不能做函数参数,为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法等;
5.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
基于auto的范围for
void test_for()
{
int array[] = { 1,5,8,7,6,9,3,2,4 };
for (auto x: array)
{
cout << x << " ";
}
}for循环后的括号由冒号“ :”分为两部分:第一部分是范 围内用于迭代的变量,第二部分则表示被迭代的范围。整个意思为,auto能识别array数组的类型,并将数组中的值逐一赋给x,然后进行输出并会自动识别循环结束的时候。我们也可以对数组进行值的改变。
八、nullptr空指针
NULL实际是一个宏,在传统的C头文件(stddef.h)中
NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。说明
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
总结:
本篇主要是关于C++的一些基础特性:命名空间、自动识别类型的输入输出、能够函数带默认值的缺省参数、一函数名多种变化的函数重载、共用同一空间的别名引用、不愿被调用的内联函数、了解你喜怒哀乐的auto、新的bug朋友nullptr空指针。