万字讲解C++基础

C++基础

  • 一、C++关键字(C++98)
  • 二、 命名空间
    • 1、命名空间的作用
    • 2、命名冲突的代码
    • 3、命名冲突代码的运行结果
    • 4、命名冲突的原因
    • 5、命名空间的定义
    • 6、命名空间的简单定义
    • 7、命名空间的嵌套定义
    • 8、命名空间的合并
    • 9、命名空间的使用
      • (1)代码
      • (2)使用using namespace 命名空间名称引入
      • (3)使用using将命名空间中某个成员引入
      • (4)加命名空间名称及作用域限定符
  • 三、 C++的输入与输出
    • 1、引入
    • 2、代码
    • 3、说明
  • 四、缺省参数
    • 1、概念
    • 2、简单的缺省参数代码
    • 3、运行结果
    • 4、全缺省参数代码
    • 5、全缺省参数运行结果
    • 6、半缺省参数代码
    • 7、半缺省参数运行结果
    • 8、注意
  • 五、内联函数
    • 1、概念
    • 2、示例
    • 3、特性
  • 六、auto关键字
    • 1、概念
    • 2、auto的简单使用
    • 3、auto与指针和引用结合
    • 4、用auto在同一行定义多个变量
    • 5、用auto最好的场景
  • 七、指针空值nullptr(C++11)
    • 1、引入
    • 2、示例代码
    • 3、运行结果与NULL定义
    • 4、解释
  • 八、引用
    • 1、概念
    • 2、引用的特性
    • 3、简单地使用引用
      • (1)代码
      • (2)运行结果
    • 4、常引用
      • (1)代码
      • (2)运行结果
    • 5、引用做参数
      • (1)代码
    • 6、引用做返回值
      • (1)代码
      • (2)TestAdd函数运行结果
      • (3)说明
    • 7、引用和指针的区别
      • (1)相同点
      • (2)不同点
    • 8、引用使用的场景
  • 九、函数重载
    • 1、概念
    • 2、代码
    • 3、运行结果
    • 4、说明
  • 十、C++支持函数重载的原理--名字修饰

一、C++关键字(C++98)

万字讲解C++基础_第1张图片

  • 此处只是提及一下C++具有的关键字,部分与C语言的关键字一致,因为C++兼容C语言。

二、 命名空间

1、命名空间的作用

在C/C++中,变量、函数以及类都是大量存在的,这些变量、函数和类的名称都将存在于全局作用域中,而它们的名称有可能一样或者和C/C++库中的一些名称相同,而这会导致很多冲突发生。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,而使用命名空间需要使用到namespace这一个关键字,即namespace的出现就是针对这种问题的。

2、命名冲突的代码

#include
using namespace std;

int rand = 1;
int main()
{
	cout << rand << endl;

3、命名冲突代码的运行结果

在这里插入图片描述

4、命名冲突的原因

因为C++标准库中有一个rand的函数,而rand是定义在全局的变量,标准库中的rand函数默认也是在全局展开的,而这两个东西的名字一样,编译器不知道要调用哪一个,所以就报错了。

5、命名空间的定义

定义命名空间,需要使用到namespace关键字,该关键字后面跟命名空间的名字,然后接一对{}即可,{}中的成员即为命名空间的成员,{}为这些成员的域。即一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

6、命名空间的简单定义

#include
using namespace std;

namespace snow
{
	int rand = 1;
	int Add(int num1, int num2)
	{
		return num1 + num2;
	}
	struct Node
	 {
		 struct Node* next;
		 int val;
	 };
}
  • {}内的成员即为命名空间snow的成员

7、命名空间的嵌套定义

namespace snow
{
	int rand = 1;
	int Add(int num1, int num2)
	{
		return num1 + num2;
	}
	namespace dragon
	{
		int num3 = 10;
		int num4 = 40;
	}
}
  • 在命名空间snow中嵌套定义了dragon命名空间。
  • 要使用dragon命名空间内的成员变量时,要先通过snow命名空间找到dragon命名空间后,再通过dragon命名空间才能找到dragon命名空间内的成员。

8、命名空间的合并

//Test.h
namespace snow
{
	int Mul(int x, int y)
	{
		return x * y;
	}
}
  • 比如在头文件Test.h中定义了一个命名空间snow,而在其他源文件中也有一个名为snow的命名空间,当在该源文件中包含Test.h头文件时,即在该源文件中写上#include"Test.h"时,这两个snow命名空间将被合并起来成为一个命名空间snow,而原本的这两个命名空间中的成员(内容)也将成为合并后的命名空间的成员。

9、命名空间的使用

(1)代码

#include
#include"Test.h"
using namespace std;

//命名空间
namespace snow
{
	//int rand = 1;		//将snow命名空间展开时,此语句最好注释掉
	int Add(int num1, int num2)
	{
		return num1 + num2;
	}
	namespace dragon
	{
		int num3 = 10;
		int num4 = 40;
	}
}

using namespace snow;
using dragon::num4;

int main()
{
	cout << Add(10, 20) << endl;
	cout << dragon::num3 << endl;
	cout << num4 << endl;
	//上方展开后,下面那样写也没问题
	cout << dragon::num4 << endl;

	cout << Mul(10, 20) << endl;

	return 0;
}

(2)使用using namespace 命名空间名称引入

  • 如上方代码中的using namespace snow;
  • 加上该式子,就说明将这个命名空间中的全部成员在全局中展开,即可以在下方直接调用该命名空间中的成员,如上方代码中的Add函数。

(3)使用using将命名空间中某个成员引入

  • 如上方代码中的using dragon::num4;
  • 因为在前面已经将snow命名空间展开了,所以dragon命名空间也在全局中了。
  • 而当我们想用dragon命名空间中的成员时还是用不了。但可以用using命名空间将想使用的成员引入,引入后就可以直接使用该成员。

(4)加命名空间名称及作用域限定符

  • 如上方代码中的dragon::num3就是要使用时再直接引入。

三、 C++的输入与输出

1、引入

写程序少不了的就是输入与输出语句,像C语言中的输出函数printf与输入函数scanf。而在C++中则是使用cout与cin来进行输出与输入,比如一个简单的C++程序可以如下方那么写。

2、代码

#include
using namespace std;

int main()
{
	int x;
	cin >> x;
	cout << "Hello world!!!" <<e ndl;
	return 0;
}

3、说明

  • 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
  • std是C++标准库的命名空间的名称,C++将标准库的定义实现都放到这个命名空间中
  • cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
  • <<是流插入运算符,>>是流提取运算符。
  • 使用C++输入输出更方便,不需要像C语言中的scanf与printf函数进行输入与输出时那样,需要手动控制格式,即写上变量的格式。C++的输入与输出可以自动识别变量类型。

四、缺省参数

1、概念

缺省参数是声明或定义函数时为函数的形参(形式参数)指定一个缺省值。在调用该函数时,如果没有传入实参则采用该函数形参的缺省值,否则使用传入的实参。

2、简单的缺省参数代码

#include
using namespace std;

void Func(int a = 10)
{
	cout << a << endl;
}
int main()
{
	Func();
	Func(20);
	return 0;
}

3、运行结果

万字讲解C++基础_第2张图片

4、全缺省参数代码

void Func(int a = 10, int b = 20, int c = 30)
{
	cout << a << " " << b << " " << c << endl;

}

int main()
{
	Func();
	Func(40);
	Func(40, 50);
	Func(40, 50, 60);
	return 0;
}
  • 全缺省参数就是函数的全部形参都有缺省值。

5、全缺省参数运行结果

万字讲解C++基础_第3张图片

6、半缺省参数代码

void Func(int a, int b = 20, int c = 30)
{
	cout << a << " " << b << " " << c << endl;

}

int main()
{
	Func(40);
	Func(40, 50);
	Func(40, 50, 60);
	return 0;
}
  • 半缺省参数就是函数的部分形参有缺省值,而不是一半数量的形参有缺省值。

7、半缺省参数运行结果

万字讲解C++基础_第4张图片

8、注意

  • 半缺省参数必须从右往左依次指定,不能间隔着指定,否则编译器无法确定传入的参数该给谁。
  • 缺省参数不能在函数声明和定义中同时出现,如果声明的位置与定义的位置同时出现缺省参数,恰巧两个位置提供的缺省参数的值不同,那么编译器无法确定到底该用哪个地方的缺省参数的值。如果声明和定义分离时,一般是在声明中给出缺省参数。
  • 缺省值必须是常量或者全局变量。
  • C语言不支持(编译器不支持)缺省参数。

五、内联函数

1、概念

用inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开该函数的函数体,没有函数调用建立栈帧的开销,内联函数能够提高程序运行的效率。

2、示例

万字讲解C++基础_第5张图片
将该函数用inline修饰后需要做一些操作才能看到inline修饰后的效果。
万字讲解C++基础_第6张图片
万字讲解C++基础_第7张图片
图中call为调用函数。

3、特性

  • inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用该函数的函数体替换函数调用。
  • inline的缺陷:可能会使目标文件变大。
  • inline的优势:减少了函数调用的开销,提高程序运行的效率。
  • inline对于编译器来说只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性,在调用该函数的地方还是会直接调用函数而不是展开函数的函数体。
  • inline修饰的函数不建议声明和定义分离,因为分离会导致链接错误。如果inline修饰的函数被展开,编译器就不会保存该函数的地址,而调用函数时只找到这个函数的声明,要找到函数的定义还需要它的地址,这会导致链接时找不到该函数的定义以及它的内容。
  • 下方为Func函数声明和定义分开时编译器报的错误。
    inlin修饰的函数声明与定义分离时报的错误

六、auto关键字

1、概念

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它。而在C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。但需要注意的是使用auto定义变量时必须对其进行初始化,因为在编译阶段编译器需要根据初始化的表达式来推导auto的实际类型。

2、auto的简单使用

int Testauto()
{
	return 20;
}
void Test1()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = Testauto();

	//输出变量的类型
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	//auto e;		//报错,需要对其进行初始化
}

万字讲解C++基础_第8张图片

3、auto与指针和引用结合

void Test2()
{
	int a = 10;
	auto b = a;
	auto* c = &a;
	auto d = &a;
	auto& e = a;

	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;
}

万字讲解C++基础_第9张图片

4、用auto在同一行定义多个变量

void Test3()
{
	auto a = 10, b = 20;
	//auto c = 30, d = 40.4;		//编译失败,因为两个变量的初始化表达式类型不同
}

当在同一行声明或者定义多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。下方为将注释展开时编译器报的错误。
在这里插入图片描述

5、用auto最好的场景

#include
#include
#include
void Test5()
{
	std::map<std::string, std::string> m;
	std::map<std::string, std::string>::iterator it1 = m.begin();
	auto it2 = m.begin();
}
  • std::map::iterator为类型,用auto时就不用写那么多了,所以it2与it1的类型是相同的。
void Test4()
{
	int arr1[] = { 1,2,3,4 };
	auto arr2[] = { 1,2,3,4 };		//不能直接用来声明数组
}

void Testauto2(auto x)		//不能作为函数形参,因为无法对其进行推导
{}

七、指针空值nullptr(C++11)

1、引入

我们在创建一个指针又不知道要将这个指针指向哪个对象时,在C语言我们会用NULL,但在C++中最好是使用nullptr。

2、示例代码

#include
using namespace std;

void Func(int x)
{
	cout << "Func(int x)" << endl;
}
void Func(int* x)
{
	cout << "Func(int* x)" << endl;
}
int main()
{
	Func(0);
	Func(NULL);
	Func((int*)NULL);
	Func(nullptr);
	return 0;
}

3、运行结果与NULL定义

万字讲解C++基础_第10张图片
万字讲解C++基础_第11张图片

4、解释

  • __cplusplus指的是C++的编译器,图中说的是如果编译器是C++编译器就将NULL定义为0,否则就定义为((void *)0)。
  • 我们可以看到在上方的代码运行结果中,NULL位置应该是调用Func(int* x)函数的,但它却调用了Func(int x)函数,说明将NULL被定义成了整数,在定义图中知晓NULL被定义成了0。
  • 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
  • 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  • 在C++11中,sizeof( nullptr ) 与 sizeof( (void *) 0 )计算所得的字节数结果相同。

八、引用

1、概念

引用是给已经存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共使用同一块内存空间。

  • 使用方法:类型& 引用变量名(对象名) = 引用实体;

2、引用的特性

  • 引用在定义时必须初始化。
  • 一个变量可以有多个引用。
  • 引用一旦引用一个实体(对象),再不能引用其他实体(对象)。

3、简单地使用引用

(1)代码

void Test1()
{
	int a = 10;
	//int ra;		//未初始化,编译时会出错
	int& ra = a;
	//double& rd = a;
	cout << a << endl;
	cout << ra << endl;


	printf("&a  = %p\n", &a);
	printf("&ra = %p\n", &ra);

}

(2)运行结果

万字讲解C++基础_第12张图片

  • 引用类型必须和引用实体是同种类型的,否则如代码中的rd,展开时编译会报错。

4、常引用

(1)代码

void Test2()
{
	const int a = 10;
	//int& ra = a;		//出错,限定符const被丢弃了
	const int& ra = a;

	const int& b = 10;

	cout << "a  = " << a << endl;
	cout << "ra = " << ra << endl;
	cout << "b  = " << b << endl;

	double d = 20;
	//int& c = d;
	const int& c = d;
	cout << "d  = " << d << endl;
	cout << "c  = " << c << endl;
}

(2)运行结果

万字讲解C++基础_第13张图片

  • const修饰的变量不能赋值给不是const修饰的引用,即权限不能放大;而相反,没有const修饰的变量却可以赋值给有const修饰的引用,即权限可以缩小。
  • 代码中b引用赋值为10,因为10是一个常量,所以b需要有const修饰符修饰为常变量,否则将报错。
  • 而d为double类型却可以赋值给c,这是因为c为常变量,赋值时不是直接将d赋值给c,而是将d赋值给一个临时变量,临时变量再赋值给c,而临时变量具有常属性。
    万字讲解C++基础_第14张图片

5、引用做参数

(1)代码

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
  • 代码中引用的作用类似于指针。

6、引用做返回值

(1)代码

int& Add(int x, int y)
{
	int z = x + y;
	return z;
}
void TestAdd()
{
	int x = 10, y = 20;
	int& ret = Add(x, y);
	cout << ret << endl;
	cout << ret << endl;

	Add(1, 2);
	cout << ret << endl;
	cout << ret << endl;
}

int& count()
{
	static int n = 0;
	++n;
	return n;
}

(2)TestAdd函数运行结果

万字讲解C++基础_第15张图片

(3)说明

  • 引用作为返回值需要注意一点,因为如果返回的是一个局部变量,当返回后,该函数将被销毁,而该局部变量(返回值)也会被销毁,这时在外部使用接收这个返回值的变量时就会出现一些错误。
  • 如上面的运行结果,当我们第一次调用Add函数时,在外部的ret是可以使用的,这是因为此时z所在的空间已经被回收了,但该空间还存在且该空间的数据未被修改,此时,z、ret是无意义的,而ret的结果是未定义的,如果栈帧结束时,系统清理栈帧并置为随机值,那么这里ret的结果就是随机值。
  • 当第二次调用该函数后,该空间又被使用,而里面的值就会被覆盖,此时该空间的值就变了。
  • 而像最后一个函数count,局部变量n用static修饰,即n所在的空间在静态区中,count函数结束后,n所在的空间不会被回收,如果没有static修饰,则函数需改为传值返回,即函数名需改为int count()。

7、引用和指针的区别

(1)相同点

在语法概念上引用就是一个别名,没有独立的空间,和其引用实体共用同一块空间。但在底层实现上却是有空间的,因为引用是按照指针的方式来实现的。
万字讲解C++基础_第16张图片
通过反汇编可以看到,引用和指针的操作是相同的。

(2)不同点

  • 引用概念上是定义一个变量的别名,指针则是存储一个变量的地址。
  • 引用在定义时必须初始化,而指针可以置为NULL(nullptr)。
  • 引用在初始化后,即引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型的实体。
  • 没有空引用,但有空指针
  • 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
  • 引用自增即引用所引用的实体增加1,指针自增即指针向后偏移一个类型的大小。
  • 有多级指针,但是没有多级引用。
  • 访问实体方式不同,指针需要显式解引用(加 * ),而引用编译器自己会处理。
  • 引用比指针使用起来相对更简单也更安全,同时可读性也比较高。

8、引用使用的场景

  • 做参数:输出型参数或者大对象传参,能够提高程序的效率。
  • 做返回值:输出型返回对象,调用者可以修改返回对象、减少拷贝,能够提高程序的效率。

九、函数重载

1、概念

函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数数量、类型、类型顺序中一个或多个)不同,常用来处理实现功能类似但数据类型不同的问题,减去了为多个功能类似的函数起名字的烦恼。

2、代码

#include
using namespace std;

void Func()
{
	cout << "Func()" << endl;
}
void Func(int x)
{
	cout << "Func(int x)" << endl;
}
void Func(double x)
{
	cout << "Func(double x)" << endl;
}
void Func(int x, double y)
{
	cout << "Func(int x, double y)" << endl;
}
void Func(double y, int x)
{
	cout << "Func(double y, int x)" << endl;
}

int main()
{
	Func();
	Func(30);
	Func(50.5);
	Func(40, 50.5);
	Func(50.5, 40);

	return 0;
}

3、运行结果

万字讲解C++基础_第17张图片

4、说明

  • 上方代码中的五个Func函数构成重载,第一个和第二个是函数参数的个数不同,第二个和第三个是参数类型不同,第四个和第五个则是参数的顺序不同。

十、C++支持函数重载的原理–名字修饰

由于Windows下的VS编译器修饰规则过于复杂,而Linux下g++编译器的修饰规则相对VS来说比较简单易懂,下面我们使用g++演示C++程序的重载函数被修饰后的函数名字,而C语言则是使用gcc编译器。
万字讲解C++基础_第18张图片

  • 通过上面的图片,我们会发现用gcc编译的函数修饰后函数名不变,而用g++编译的函数修饰后函数名变成(_Z+函数长度+函数名+类型首字母)。
  • 如果两个函数的函数名和参数是一样的,只有返回值不同是不构成函数重载的,因为C++编译器编译后,两个函数的函数名是一样的,而调用时编译器没办法区分这两个函数名。

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请务必点赞、收藏加关注

你可能感兴趣的:(C++,c++,开发语言,c语言,visual,studio)