C++入门基础

一、命名空间

在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全 局作⽤域中,可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名 冲突或名字污染,namespace关键字的出现就是针对这种问题的。

1.1 命名空间的价值

c语言项目普遍存在着命名冲突的问题,C++引入namespace就是为了更好的解决这一问题。

我们来看下面的代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}

如果我们编译上面的代码,编译器会给我们报一个错误:"rand": 重定义;以前的定义是"函数"

为了解决这样的问题,在C++中引入了命名空间。

2.2 命名空间的定义

定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中
即为命名空间的成员。命名空间中可以定义变量/函数/类型等。

1.命名空间的普通定义:

cpp 复制代码
namespace zj
{
	int a;
	double b;
	int add(int x, int y)
	{
		return x + y;
	}
}

这就是命名空间的普通定义,在命名空间内既可以定义变量也可以定义函数,zj是命名空间的名字。

2.命名空间的嵌套定义:

cpp 复制代码
namespace zj
{
	int a;
	double b;
	int add(int x, int y)
	{
		return x + y;
	}
	namespace zhangsan
	{
		int x = 0;
		int add(int x, int y)
		{
			return (x + y) * 10;
		}
	}
}

命名空间只能定义在全局,但命名空间是可以嵌套的,在一个命名空间内输入另一个命名空间。

3.项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。

cpp 复制代码
namespace zj
{
	int a;
	double b;
}

namespace zj
{
	int add(int x, int y)
	{
		return x + y;
	}
}

编译器最终会合成到一个命名空间中。

2.3 命名空间的使用

编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:

  1. 指定命名空间访问,项⽬中推荐这种⽅式。
  2. using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
  3. 展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。
cpp 复制代码
#include<stdio.h>
namespace zj
{
	int a = 0;
	int b = 1;
}
int main()
{
	printf("%d\n", a);
	return 0;
}

我们在执行上述代码时,会发生报错,显示a是未声明的标识符。

这其中的原因在于编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找,所以我们查找不到命名空间里的a变量,导致了这个报错。

下面我们来讲讲命名空间的三种使用方法:

1.指定命名空间访问

先看下列代码:

cpp 复制代码
#include<stdio.h>
namespace zj
{
	int a = 0;
	int b = 1;
}

namespace zhangsan
{
	int a = 1;
	int b = 2;
}
int main()
{
	printf("%d\n", zj::a);
	printf("%d\n", zhangsan::a);
	return 0;
}

在上述代码中我创建了两个命名空间,一个是zj一个是zhangsan,他们中都创建了两个变量,我们可以使用::来访问指定的命名空间,像zj::a就是访问zj命名空间的a变量。

结果:


2. using将命名空间中某个成员展开
先看下列代码:

cpp 复制代码
#include<stdio.h>
namespace zj
{
	int a = 0;
	int b = 1;
}

namespace zhangsan
{
	int a = 1;
	int b = 2;
}
using zhangsan::a;
int main()
{
	printf("zj::a=%d\n", zj::a);
	printf("zhangsan::a=%d\n", a);
	return 0;
}

与第一种方法不同的是,我们增加了using zhangsan::a,他的意思是将zhangsan命名空间中的a成员展开,这样我们便能在全局中查找到zhangsan命名空间的a变量,所以我们将第二个printf的输出修改为a,结果与第一种方法是一样的。

结果:

3.展开命名空间中全部成员
先看下列代码:

cpp 复制代码
#include<stdio.h>
namespace zj
{
	int a = 0;
	int b = 1;
}

namespace zhangsan
{
	int a = 1;
	int b = 2;
}
using namespace zhangsan;
int main()
{
	printf("zj::a=%d\n", zj::a);
	printf("zhangsan::a=%d\n", a);
	printf("zhangsan::b=%d\n", b);
	return 0;
}

与第二种方法只将命名空间内的某一个成员展开不同,第三种方法是将命名空间全部展开,像上述代码便将命名空间zhangsan全部展开,所以我们在全局中能找到命名空间zhangsan的a、b变量,所以我们在printf上输出的a、b变量其实就是命名空间zhangsan内的变量a、b.
结果:

虽然第三种方法能使得命名空间全部展开,但是这种方法却有很大的弊端,如果我们在命名空间zhangsan内加入一个变量为printf,那么这个程序便会报错。

加入了printf变量后,便于输出函数printf重名了,导致编译器识别不出来printf是输出函数还是常量printf,就会报错,所以这种方法是是有很大弊端的,如果你将两个或以上的命名空间同时展开,而且这些命名空间里又有重名的变量,便会报错,所以不推荐使用这种方法。

二、C++的输入输出

C++的输入输出函数包含在头文件<iostream>中,<iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。

相比与C语言的输入输出函数,C++的输入输出函数要更加高级,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输入输出可以⾃动识别变量类型,其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出。

在C++中,cout是输出函数,cin是输入函数,endl是换行,这三个函数都属于C++标准库,而C++的标准库都存放在命名空间std中,所以我们要通过命名空间的使用方式去使用他们,一般在日常生活的练习当中,为了寻求方便,我们一般会将整个命名空间std全部展开,当然也仅限于日常练习,在其他时候还是不要轻易的将命名空间全部展开,前面已经说过了他的弊端。

下面我们通过一段简单的代码来了解C++的输入输出。

cpp 复制代码
int main()
{
	int a;
	double b;
	cin >> a;
	cin >> b;
	cout << "" << a << " " << b << endl;
	return 0;
}

结果:

三、缺省参数

缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参
则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。
下面我们来看一个简单的例子:

cpp 复制代码
void func(int x = 20)
{
	cout << "" << x << endl;
}


int main()
{
	func();
	func(10);
	return 0;
}

函数的参数int x=20就是缺省参数,在调用函数时,如果没有传递参数,那么就会采用缺省参数,传递了参数就要所传递的参数。

结果:

可以看到,在未传递参数时,输出的值为缺省参数20,传递参数时,输出的便是所传递的参数。

3.1 全缺省

全缺省就是全部形参给缺省值。

cpp 复制代码
void func(int x = 20,int y=30,int z=40)
{
	cout << "" << x+y+z << endl;
}


int main()
{
	func();
	func(10);
	func(10, 20);
	func(10, 20, 30);
	return 0;
}

如上图代码所示,函数func就是全缺省函数。

结果:

3.2 半缺省

半缺省就是部分形参给缺省值C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。

cpp 复制代码
void func(int x ,int y=30,int z=40)
{
	cout << "" << x+y+z << endl;
}


int main()
{
	func(10);
	func(10, 20);
	func(10, 20, 30);
	return 0;
}

上图所示的函数func便是半缺省函数,因为半缺省函数不能间隔跳跃给缺省值,所以像这种形式void func(int x ,int y=30,int z)的半缺省便是错的。

结果:

注意:函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值

四、函数重载

C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者 类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。而 C语⾔是不⽀持同⼀作⽤域中出现同 名函数的。

4.1 参数类型不同

我们来看下面的代码:

cpp 复制代码
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

int main()
{
	int ret1=Add(3, 5);
	cout << "" << ret1 << endl;
	double ret2=Add(3.4, 6.6);
	cout << "" << ret2 << endl;
	return 0;
}

这便是参数类型不同的函数重载。

结果:

4.2 参数个数不同

来看下面的代码:

cpp 复制代码
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}

int main()
{
	f();
	f(10);
	return 0;
}

这便是参数个数不同的函数重载。

结果:

4.3 参数类型顺序不同

来看下面的代码:

cpp 复制代码
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}

int main()
{
	f(3, 'a');
	f('a', 3);
	return 0;
}

这便是参数类型顺序不同的函数重载。

结果:

五、引用

引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间, 它和它引⽤的变量共⽤同⼀块内存空间。⽐如:⽔壶传中李逵,宋江叫"铁⽜",江湖上⼈称"⿊旋风";林冲,外号豹⼦头;

类型& 引⽤别名 = 引⽤对象;

cpp 复制代码
int main()
{
	int b = 10;
	int& c = b;
	cout << "" << &b << endl;
	cout << "" << &c << endl;
}

上述代码便是引用,相当于给b取了个别名叫c,输出的结果我们能看到b和c的地址应该是一样的。

结果:

5.1 引用的特性

  1. 引⽤在定义时必须初始化
  2. ⼀个变量可以有多个引⽤
  3. 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
  4. 引⽤在定义时必须初始化

不初始化的话编译器会报错。

2.⼀个变量可以有多个引⽤

可以看见,对于a的其他引用的地址都是a的地址。

  1. 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体

b是a的引用,c的值为20,b=c其实是将c的值赋给b,而不是将b改为c的引用,所以最后a的值被改为了20。

5.2 引用的使用

1.引用做参数

引用做参数就和指针做参数一样,形参可以影响到实参,我们用最经典的swap函数来验证一下。

cpp 复制代码
void Swap(int& rx, int& ry)
{
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main()
{
	int x = 0, y = 1;
	cout << x << " " << y << endl;
	Swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

这就代表着以后我们想要让形参影响实参,可以不再传指针,而是可以传引用。

5.3 const引用

可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访
问权限在引⽤过程中可以缩小,但是不能放⼤。

下面我来距离几种const引用比较容易出错的地方:

这时第一种可能出错的情况,这里的引用是对a访问权限的放大,在int前加个const就好。

这时第二种可能出现的情况,因为const修饰的常量,所以我们不能对常量做任何修改。

cpp 复制代码
int main()
{
	const int a = 10;
	const int& ra = a;


	int b = 20;
	const int& rb = b;
	return 0;
}

这两种对const的引用才是正确的。

六、引用与指针的关系

C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。

  1. 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
  2. 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  3. 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
  4. 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
  5. sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
  6. 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。

七、内联函数

⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就需要建⽴栈帧了,就可以提⾼效率。
C++设计inline的目的就是为了替代C语言的宏函数,因为C语言的宏函数是很复杂且很容易出错的。
这里要注意的是, inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展 开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁 调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
那么该如何判断内联函数是否被展开了呢,我们可以通过以下代码来判断:

cpp 复制代码
inline int Add(int x, int y)
{
	int ret = x + y;
	ret += 1;
	ret += 1;
	ret += 1;
	return ret;
}
int main()
{
	int ret = Add(1, 2);
	cout << Add(1, 2) * 5 << endl;
	return 0;
}

这时我们通过调试进到反汇编中,如果在反汇编中,发现有call Add语句就是没有展开,否则就是展开了

我们可以看到,里面的指令有call且有Add函数,所以内联函数Add是没有被展开的,大家也可以删减一下代码再去看看是否被展开。

八、nullptr

nullptr是C++中的空指针,与C语言的空指针NULL不同,在C++中,NULL代表的是0。
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:

cpp 复制代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

所以在C++中,NULL会被定义为字面常量0,所以C++
引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。
我们通过下面的代码来验证一下:

cpp 复制代码
#include<iostream>
using namespace std;
void f(int x)
{
	cout << "f(int x)" << endl;
}
void f(int* ptr)
{
	cout << "f(int* ptr)" << endl;
}
int main()
{
	f(0);
    f(NULL);
	f((int*)NULL);	
	f(nullptr);
	return 0;
}

结果:

由此可见,0和NULL都被当做是常量,将NULL强制转换为int*也变成了指针,所以(int*)NULL和nullptr都是指针。

九、总结

上面所讲的都是C++一些入门的基础知识,如果觉得有帮助的话,别忘了一键三连,谢谢各位。

相关推荐
wrx繁星点点15 分钟前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
真的想不出名儿19 分钟前
Java基础——反射
java·开发语言
努力编程的阿伟38 分钟前
【Java SE语法】抽象类(abstract class)和接口(interface)有什么异同?
java·开发语言
Dola_Pan1 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
包饭厅咸鱼1 小时前
QML----复制指定下标的ListModel数据
开发语言·数据库
bryant_meng1 小时前
【python】Distribution
开发语言·python·分布函数·常用分布
红黑色的圣西罗1 小时前
Lua 怎么解决闭包内存泄漏问题
开发语言·lua
yanlou2331 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程1 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
墨墨祺1 小时前
嵌入式之C语言(基础篇)
c语言·开发语言