目录
1.命名空间
在C语言中,一个工程内是无法定义两个具有相同变量名的变量的,因为这会产生命名冲突。在C++中有了解决命名冲突的办法,这就是命名空间。
(1)定义与使用
定义命名空间方法:namespace 自定义空间名称 { } (花括号内部放元素)
使用命名空间方法:自定义空间名称 :: 变量名;
下面将会通过代码讲解命名空间的使用。(本文代码运行环境均为win10系统下的VS2019)
代码一:可以分别在N1和N2中定义具有相同变量名的变量,从而解决命名冲突的问题。
//代码一
#include <stdio.h>
namespace N1 {
int a = 1;
}
namespace N2 {
int a = 2;
}
int main() {
printf("N1::a=%d\n", N1::a);//输出 N1::a=1
printf("N2::a=%d\n", N2::a);//输出 N2::a=2
}
代码二:命名空间中也可以定义并实现函数。
//代码二
#include <stdio.h>
namespace N1 {
void Add(int m, int n) {
printf("N1::Add ");
printf("%d+%d=%d\n", m, n, (m + n));
}
}
int main() {
N1::Add(3, 5);//输出 N1::Add 3+5=8
}
代码三:命名空间可以嵌套定义。
//代码三
#include <stdio.h>
namespace N1 {
namespace N2 {
int a = 3;
}
}
int main() {
printf("N1::N2::a=%d\n", N1::N2::a);//输出 N1::N2::a=3
}
代码四:同一个C++工程中可以存在空间名称相同的的命名空间。但要注意,相同名称的命名空间内不能有相同名称的变量,因为编译器会将名字相同的空间进行合并。
//代码四
#include <stdio.h>
namespace N1 {
int a = 1;
}
namespace N1 {
int b = 2;
}
int main() {
printf("N1::a=%d\n", N1::a);//输出 N1::a=1
printf("N1::b=%d\n", N1::b);//输出 N1::b=2
}
(2)特殊用法(就近原则)
代码五:代码中有两个a变量,一个是全局变量,一个是main函数中的变量。
//代码五
#include <stdio.h>
int a = 100;
int main() {
int a = 5;
printf("%d\n", a);//输出 5
printf("%d\n", ::a);//输出 100
}
在打印时,遵循就近原则,默认打印main函数中的a。若要打印全局变量a,需要以此种格式打印:printf("%d\n", ::a);。至于为什么前面不加其他的符号,是因为全局作用域只有一个,不需要为它起名字,而别的命名空间则可能有很多,需要用名字来区分。
(3)设置全局
代码六:添加此语句:using 空间名称 :: 变量名。语句含义是:将N1中的a变量当作一个全局变量。
//代码六
#include <stdio.h>
namespace N1 {
int a = 1;
}
using N1::a;
int main() {
printf("%d\n", a);//输出 1
}
代码七:与上述同理,将N1中的Add函数当作全局函数。
//代码七
#include <stdio.h>
namespace N1 {
void Add(int m, int n) {
printf("N1::Add ");
printf("%d+%d=%d\n", m, n, (m + n));
}
}
using N1::Add;
int main() {
Add(3, 4);//输出 N1::Add 3+4=7
}
代码八:当命名空间内很多个变量都需要当作全局变量时,上述设置显然太麻烦。可使用以此方法: using namespace 命名空间名称。语句含义是:将此空间内所有内容都当作全局变量。
//代码八
#include <stdio.h>
namespace N1 {
int a = 1;
void Add(int m, int n) {
printf("N1::Add ");
printf("%d+%d=%d\n", m, n, (m + n));
}
}
using namespace N1;
int main() {
printf("%d\n", a);//输出 1
Add(3, 4);//输出 N1::Add 3+4=7
}
2.C++输入输出
C语言中的printf函数有时候还是比较麻烦的,所以C++中又提供了一种新的输入输出的方法。那就是输出(cout),输入(cin)。这两个函数的头文件都是 “iostream”,使用时需要包含对应头文件。
(1)输出(cout)
cout函数在std空间中,使用时需加上空间名:std::cout
代码九:cout函数可以直接输出多种格式的数据,而不用添加printf函数中所需要的 %d 之类的符号。endl代表换行(endl也在std空间中)。
//代码九
#include "iostream"
int main() {
int a = 10;
char c = 'm';
const char* s = "abc";
std::cout << "Hello World!" << std::endl;//输出 Hello World!
std::cout << a << std::endl;//输出 10
std::cout << c << std::endl;//输出 m
std::cout << s << std::endl;//输出 abc
}
(2)输入(cin)
代码十:cin函数可以直接接收多种格式的数据,而不用添加scanf函数中所需要的 %d 之类的符号。
//代码十
#include "iostream"
int main() {
int a;
double b;
std::cin >> a;
std::cin >> b;
std::cout << "================" << std::endl;
std::cout << a<< std::endl;//输出 a的值
std::cout << b << std::endl;//输出 b的值
}
(3)总结
代码十一:以上的使用方法还是有些繁琐,所以按之前说过的方法,设置全局:using namespace std;
//代码十一
#include "iostream"
using namespace std;
int main() {
int a = 10;
cout << a << endl;//输出 10
}
3.C语言和C++函数的区别
代码十二:这段代码是C语言代码,运行成功。无参的函数在加上参数后依然可以运行成功。
//代码十二
#include <stdio.h>
void Prin() {
printf("Hello World!\n");
}
int main() {
Prin();//输出 Hello World!
Prin(10);//输出 Hello World!
}
代码十三:这段代码是C++代码,运行时会报错。 无参的函数加上参数后会报错。
//代码十三
#include <stdio.h>
void Prin() {
printf("Hello World!\n");
}
int main() {
Prin();
Prin(10);//报错信息:E0140 函数调用中的参数太多
}
结论:C++代码在检测时比C语言严格。
4.缺省
(1)缺省的定义
缺省参数:声明或定义函数时,为参数设置一个默认值,在函数调用时,如果传递了实参,就使用用户传递的实参,否则使用设置的默认值。
现实例子:备胎。
要点:
1.为参数设置默认值必须从右向左依次设置,不能从左往右设置。
2.参数可以设置全缺省或半缺省。
3.声明和定义不能同时给出缺省,因为声明与定义可能给出的默认值不一样,编译器就不知道用哪个了,就会报错。
4.缺省值必须是常量或全局的
(2)全缺省
代码十四:以下代码是全缺省,函数参数全部都有默认值。
//代码十四
#include "iostream"
using namespace std;
void Add(int a = 3, int b = 6) {
cout << "a+b=" << a + b << endl;
}
int main() {
Add();//输出 a+b=9
}
(3)半缺省
半缺省:半缺省并不是一半参数有默认值,而是指部分参数具有默认值。
代码十五:以下代码是半缺省,半缺省需要从右向左给出参数的默认值。
//代码十五
#include "iostream"
using namespace std;
void Add(int a, int b =1,int c = 7) {
cout << "a+b+c=" << a + b + c<< endl;
}
int main() {
Add(2);//输出 a+b+c=10
}
5.函数重载
函数重载类似于现实中的一词多义。
(1)重载概念
概念:在同一作用域中声明几个功能类似的同名函数,常用来处理功能类似数据类型不同的问题。
重载要求:形参列表(参数个数或者类型或者顺序)必须不同。函数重载与返回值类型是否相同无关。
(2)代码讲解
代码十六:实现函数重载来计算多种类型的加法。函数参数个数不同。
//代码十六
//函数参数个数不同
#include "iostream"
using namespace std;
void Add(int a, int b, int c) {
cout << "三个参数 a+b+c = " << a + b + c << endl;
}
void Add(int a, int b) {
cout << "两个参数 a+b = " << a + b << endl;
}
int main() {
Add(1, 2, 3);//输出 三个参数 a+b+c = 6
Add(1, 2);//输出 两个参数 a+b = 3
}
代码十七:实现函数重载来计算多种类型的加法。函数参数类型不同。
//代码十七
//函数参数类型不同
#include "iostream"
using namespace std;
void Add(int a, int b, int c) {
cout << "int类型 a+b+c = " << a + b + c << endl;
}
void Add(double a, double b, double c) {
cout << "double类型 a+b+c = " << a + b + c << endl;
}
int main() {
Add(1, 2, 3);//输出 int类型 a+b+c = 6
Add(1.1, 2.1, 3.1);//输出 double类型 a+b+c = 6.3
}
代码十八:实现函数重载来计算多种类型的加法。函数参数顺序不同。
//代码十八
//函数参数顺序不同
#include "iostream"
using namespace std;
void Add(int a, double b) {
cout << "先int后double a+b = " << a + b << endl;
}
void Add(double a, int b) {
cout << "先double后int a+b = " << a + b << endl;
}
int main() {
Add(1, 2.2);//输出 先int后double a+b = 3.2
Add(1.6, 2);//输出 先double后int a+b = 3.6
}
6.extern的使用
(1)使用场景
首先需要知道这件事:C++和C语言在编译时,都会对函数进行重命名,且两者的重命名方式并不一样。目前只需要知道这个就好,后面我会专门写一篇关于函数重命名的博客。
当一个C++工程需要使用一个C语言写的库,或者C语言的工程要使用C++写的库,因为函数重命名方式不同,自然无法使用,会报错。
extern则被用来解决这种问题。
(2)使用方法
在C++中,entern "C" 后面跟函数,代表将此函数按C语言方式编译,但一般不会这么使用,因为extern关键字通常用来处理库文件。
代码十九:一个简单的 entern "C" 的运用
//代码十九
#include "iostream"
using namespace std;
extern "C" void Add(int a, double b) {
cout << "a + b = " << a + b << endl;
}
int main() {
Add(3,4);//输出 a + b = 7
}
7.C++引用
(1)引用概念
引用概念:引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟空间。引用和变量共用同一块内存空间。
举例:李逵 ,外号黑旋风。黑旋风就是他的引用。
(2)引用的简单使用
使用方法:类型&引用变量名(对象名)=引用实体。(引用类型和引用实体必须是同一类型的)
代码二十:通过一个简单的引用来快速上手。如下代码中可以看出,当引用ra改变后,引用实体a也会随之改变。那么这样的特性如果运用到函数中是什么样的?
//代码二十
#include "iostream"
using namespace std;
int main() {
int a = 1;
int& ra = a;
cout << "ra = " << ra << endl;//输出 ra = 1
ra = 2;
cout << "ra = " << ra << endl;//输出 ra = 2
cout << "a = " << a << endl;//输出 a = 2
}
代码二十一:在C语言阶段,经常会写一个函数用来交换两个参数的值,我们一直用的是指针来交换。但现在学习了引用后,我们可以看到引用的改变会引起引用实体的改变,那么我们用引用作为参数来尝试实现交换函数。(如果不想让引用类型的参数修改实体的数据,可以将参数设置为常引用)
注意:传参时传地址和传引用的速率差不多,这两个的的速率都比传值要快得多!!
//代码二十一
#include "iostream"
using namespace std;
void swap(int& ra, int& rb) {
int temp = ra;
ra = rb;
rb = temp;
}
int main() {
int a = 1, b = 2;
swap(a, b);
cout << "a = " << a << endl << "b = " << b << endl;//输出 a = 2,b = 1
}
代码二十二:引用作为函数返回值类型。
注意:如果以引用方式作为函数的返回值类型,不能返回函数栈上的空间。如果要返回,返回的实体的生命周期必须比函数的生命周期长。因为如果返回的是局部变量,就会出现一系列问题。这里提供一段代码二十三,读者可以自行尝试。
//代码二十二
#include "iostream"
using namespace std;
int temp = 0;//将返回实体 temp 定义为全局变量,生命周期就比函数长。
int& Add(int a, int b) {
temp = a + b;
return temp;
}
int main() {
int& r = Add(10, 20);
cout << "r = " << r << endl; //输出 r = 30
}
代码二十三:引用作为函数返回值类型时,返回的实体生命周期并不大于函数的生命周期。(看到打印结果有没有觉得神奇)
//代码二十三
#include "iostream"
using namespace std;
int& Add(int a, int b) {
int temp = a + b;
return temp;
}
int main() {
int& r = Add(10, 20);
cout << "r = " << r << endl;//输出 r = 30
Add(40, 50);
cout << "r = " << r << endl;//输出 r = 90
Add(20, 90);
cout << "r = " << r << endl;//输出 r = 110
}
(3)常引用
常引用是一种被const修饰的引用,被const修饰的值无法被修改。
代码二十四:下面代码中的语句(1)(2)均是错误的。错误原因如下:
语句(1):因为a现在是const类型,不可以被修改,如果用普通类型的引用就有修改a的风险。
语句(2):因为10是常量,不可以被修改,无法使用普通类型的引用。
//代码二十四
#include "iostream"
using namespace std;
int main() {
const int a = 10;
//int& ra = a; (1) 错误
const int& ra = a; //正确
//int& rb = 10; (2) 错误
const int& rb = 10; //正确
}
(4)引用特性
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用一个实体,再不能引用其他实体。
8.内联函数
(1)概念
内联函数概念:C++中用inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
(2)验证内联函数的准备工作
1.在使用VS2019验证时发现,如果程序处于debug模式,需要对编译器进行设置,否则内联函数不会展开(因为dsbug模式下,编译器不会对代码进行优化)。
2.当VS2019处于release模式下,查看编译器生成的汇编代码中是否存在call+内联函数名这样的指令,如果存在说明没有展开。(在汇编中,call指令是函数调用的标志,如果没有call,说明函数直接展开,没有被调用)
(3)对编译器的设置
将需要验证的文件右键单击后按如下步骤进行:
(4)通过汇编代码展示
代码二十五:非内联函数。
//代码二十五
#include "iostream"
using namespace std;
int Add(int a, int b) {
return a + b;
}
int main() {
int ret = 0;
int a = 1, b = 2;
ret = Add(a, b);
return 0;
}
上述代码的汇编:
通过汇编指令发现:非内联函数在函数调用时会有call指令。
代码二十六:内联函数。
//代码二十六
#include "iostream"
using namespace std;
inline int Add(int a, int b) {
return a + b;
}
int main() {
int ret = 0;
int a = 1, b = 2;
ret = Add(a, b);
return 0;
}
上述代码的汇编:
可以看到,内联函数的函数并没有call指令,也就是说,并没有函数调用发生。
(5)内联函数特性
1.内联函数是一种用空间换时间的做法,通过直接展开函数来节省调用函数的开销。所以代码很长或者有循环/递归的函数不适合作为内联函数。
2.inline对于编译器而言仅仅是一个优化,如果定义为inline的函数体内有循环或递归等,编译器会忽略掉内联,改为函数调用。(很智能,可以这么说,如果内联不划算,就会改成函数调用)
3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
9.auto关键字
(1)auto的概念
auto在C++11中的全新含义:作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时推导而得到。
(2)注意事项
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,编译器在编译期间会将auto替换为变量实际的类型。
(3)auto的使用
1.auto与指针结合(* 可以省略)
代码二十七:展示auto和指针结合的使用场景
//代码二十七
#include "iostream"
using namespace std;
int main() {
auto a = 10;
cout << "a的类型:" << typeid(a).name() << endl;//输出 a的类型:int
auto pa = &a;
cout << "pa的类型:" << typeid(pa).name() << endl;//输出 pa的类型:int *
auto* pp = &a;
cout << "pp的类型:" << typeid(pp).name() << endl;//输出 pp的类型:int *
}
2.auto与引用结合(& 不可以省略)
代码二十八:auto与引用结合的场景
//代码二十八
#include "iostream"
using namespace std;
int main() {
int a = 10;
auto& ra = a;
cout << "ra的类型:" << typeid(ra).name() << endl;//输出 ra的类型:int
}
3.同一行用auto定义多个变量。注意:在同一行声明多个变量时,这些变量必须是相同类型。因为编译器实际只会对第一个变量的类型进行推导,用推导出的类型定义其他变量。
代码二十九:此场景下,同一行中如果变量类型不同会直接报错:
//代码二十九
#include "iostream"
using namespace std;
int main() {
auto a = 10, b = 20;
cout << "a的类型:" << typeid(a).name() << endl;//输出 a的类型:int
cout << "b的类型:" << typeid(b).name() << endl;//输出 b的类型:int
//auto c = 10,d = 20.5; 报错
}
(4)auto无法推导的场景
1.auto不能作为函数的参数
代码三十:此段代码会直接报错,auto不可以作为函数参数!
//代码三十
#include "iostream"
using namespace std;
void Prin(auto a) { //直接报错 报错信息:E1598 此处不允许使用"auto"
cout << a << endl;
}
int main() {
//Prin();
}
2.auto不能直接用来声明数组
代码三十一:auto不可用来声明数组,会报错。
//代码三十一
#include "iostream"
using namespace std;
int main() {
//auto arr[] = { 1,2 }; 报错
}
10.范围for循环
(1)使用条件与语法
使用条件:遍历一个有范围的集合。
语法:for(类型 变量名 :集合名)
(2)快速上手
代码三十二:快速学会使用范围for循环。其中类型可以直接设置为auto
//代码三十二
#include "iostream"
using namespace std;
int main() {
int arr[] = { 1,2,3 };
for (int e : arr)
cout << e;
cout << endl << "==========" << endl;
for (auto e : arr)
cout << e;
}
11.指针空值nullptr(C++11)
(1)C++98中的指针空值
C++98中的指针空值为NULL。但是NULL其实是一个宏,它可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。在使用时NULL可能会带来不便。
(2)C++11中的指针空值
C++11中的指针空值是nullptr。
注意:
1.使用nullptr代表指针空值时,不需要包含头文件,因为nullptr是作为C++11新关键字引入。
2.C++11中,sizeof(nullptr)与sizeof((void*)0)所占字节数相同。
3.为提高代码健壮性,后续表示指针空值时应采用nullptr。