初识C++&&快速上手

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
", N1::a);//输出 N1::a=1
    printf("N2::a=%d
", N2::a);//输出 N2::a=2
}

代码二:命名空间中也可以定义并实现函数。

//代码二
#include <stdio.h>

namespace N1 {
	void Add(int m, int n) {
		printf("N1::Add ");
		printf("%d+%d=%d
", 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
", 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
", N1::a);//输出 N1::a=1
	printf("N1::b=%d
", N1::b);//输出 N1::b=2
}

(2)特殊用法(就近原则)

代码五:代码中有两个a变量,一个是全局变量,一个是main函数中的变量。

//代码五
#include <stdio.h>

int a = 100;

int main() {
	int a = 5;
	printf("%d
", a);//输出 5
	printf("%d
", ::a);//输出 100
}

在打印时,遵循就近原则,默认打印main函数中的a。若要打印全局变量a,需要以此种格式打印:printf("%d ", ::a);。至于为什么前面不加其他的符号,是因为全局作用域只有一个,不需要为它起名字,而别的命名空间则可能有很多,需要用名字来区分。

(3)设置全局

代码六:添加此语句:using 空间名称 :: 变量名。语句含义是:将N1中的a变量当作一个全局变量。

//代码六
#include <stdio.h>

namespace N1 {
	int a = 1;
}
using N1::a;

int main() {
	printf("%d
", a);//输出 1
}

代码七:与上述同理,将N1中的Add函数当作全局函数。

//代码七
#include <stdio.h>

namespace N1 {
	void Add(int m, int n) {
		printf("N1::Add ");
		printf("%d+%d=%d
", 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
", m, n, (m + n));
	}
}
using namespace N1;

int main() {
	printf("%d
", 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!
");
}

int main() {
	Prin();//输出 Hello World!
	Prin(10);//输出 Hello World!
}

代码十三:这段代码是C++代码,运行时会报错。 无参的函数加上参数后会报错。

//代码十三
#include <stdio.h>

void Prin() {
	printf("Hello World!
");
}

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。

经验分享 程序员 微信小程序 职场和发展