一、命名空间
我们先来看这样两段代码:
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
printf("%d ",rand);return 0;
}
#inlude<stdio.h>
int a=10;
int main()
{
int a = 0;
printf("%d ",a);
return 0;
}
第一段代码中我们运行会看到显示“rand”重定义,这是因为rand函数包含在头文件stdlib.h中,我们再定义一个rand函数就会是重定义了。
第二段代码中我们进行运行发现没什么问题,这是因为会优先考虑离其近的局部变量,但是我们无法将这个全局变量“a”进行输出。
显然,我们在代码的学习中往往会定义许多的变量,他们又全局变量也有局部变量,随着学习的深入我们创建的变量也会增多,也难免会碰到变量名重复的情况,即使我们刻意去防止变量的重复,但是如果我们想要表示两种不同地方的同一种东西,我们要是对变量进行略微的改动来进行区分,会特别的容易混淆,于是我们就在想,能不能有点标志性的东西来进行相似变量之间的区分呢,加上前缀之类的显然是一个明智的方法。
在之前我们也学过一个类型叫做结构体,我们要使用结构体内的变量也是需要一步步引用出来的,于是我们有了命名空间这一说法,来便于我们进行区分和使用。
接下来我们看看其定义与使用:
我们定义命名空间需要使用到关键字“namespace”,在其后面接上命名空间的名字再加上“{}”就可以啦,{}中即为命名空间的成员。接下来我们来看下面一段代码来使用与理解一下:
#include<stdio.h>
#include<stdlib.h>
namespace bit
{
int rand = 10;
int Add(int left,int right)
{
return left + right;
}
}
int Add(int left,int right)
{
return left + right;
}
int main
{
int left = 3;
int right = 4;
printf("%d\n",Add::rand);
printf("%d\n",Add(left,right));
printf("%d\n",bit::Add(left,right));
return 0;
}
在上面一段代码中我们运行一下就可以看到并没有出现报错,这是因为我们调用的是bit中的rand,并没有与stdlib.h中的rand函数发生冲突,与结构体内部的数据有点相似,这样写的uha我们发现他与单纯的rand并不一样,如果我们在没有冲突的条件下想调用命名空间内的数据,那么我们就可以试着将内部成员引入,这时我们就需要使用我们的using namespace了,我们使用这个可以将整个空间释放或者选择将其中的某个需要多次使用且不会冲突的变量、函数释放。我们直接来看例子:
#include<stdio.h>
#include<stdlib.h>
namespace bit
{
int rand = 10;
int Add(int left,int right)
{
return left + right;
}
}
using namespace bit::Add;
//我们在外面并没有给出add函数
int main
{
int left = 3;
int right = 4;
printf("%d\n",Add::rand);
printf("%d\n",Add(left,right));//此时我们仍旧可以调用Add函数。
return 0;
}
在本串代码中,我们将bit引入,即使我们没有通过之前的访问方式,直接使用该函数依旧可以成功,这就是using namespace的功效。在结合具体的需求等条件,我们自然也延伸出了其多种使用方式。
在我们练习其使用时,往往涉及不到冲突,我们就可以直接使用using namespace bit来直接引入整个空间。在较为大型的项目中我们就可能会采取只引入空间中的某一变量或者函数来避免冲突,就比如上面的using namespace bit::Add。
二、C++的输入输出
在C语言中我们使用输入输出一般是使用sacnf与printf,并且需要包含stdio.h的头文件,在使用scanf时也需要调取变量的地址,我们来看看输入输出在C++中的表现:
#include<iostream>
using namesapce std;
int main()
{
int a = 0;cin >> a;//输入a的值
cout << a << endl;//输出a的值
return 0;
}
这是一个简单的C++输入输出程序,我们定义一个变量a,输入其值并将其输出。
在C++中,我们使用cin进行输入,使用cout进行输出,不过需要注意的时我们在使用之前需要包含iostream的头文件,以及按命名空间使用std,cin与cout是全局流的对象,cout后面的endl的是C++中的特殊符号,表示换行输出。<<是流插入运算符,>>是流提取运算符。相比于C语言,使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型,实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,我们将会在后续中深入学习。
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应
头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,
规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因
此推荐使用<iostream>+std的方式。
我们来写一个代码体现其可以自动识别类型的能力:
#include<iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin>>a;
cin>>b>>c;
cout<<a<<endl;
cout<<b<<" "<<c<<endl;
return 0;
}
在这段代码中我们可以看到我们输入了a,b,c三个不同类型的变量,但是并没有与C语言一样硬各种符号区别,直接就可输入,而且编译并没有因此报错,说明了其是具有自动识别类型的能力的。
关于std命名空间的使用惯例,我们在平时的学习过程中由于涉及冲突的可能性不打,一般直接使用using namespace std即可,但是在后续工作项目中回游大量的变量、函数,可能会引发冲突,如果我们需要使用cin与cout我们建议直接使用using std::cout/cin即可。
三、缺省函数
1.缺省函数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。我们直接来看一下这个简单的例子:
void func(int a = 0)
{
cout<<a<<endl;}
int main()
{
func();//没有传参的时候使用默认值func(10);//传参使用参数值
return 0;
}
我们调用了两次func函数,第一次没有进行传值,第二次我们进行了传值,于是他输出了0和10两次值,没有传值就传默认值,有传值就使用传值。
2.缺省函数的分类
全缺省函数
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
像这样的一串函数,我们直接就给函数的每个参数都进行了赋值操作,这就叫做全缺省函数。
半缺省函数
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
像这样的一串函数我们并没有给参数a赋值,在a之后的函数就进行了赋值,参数一班赋值一般不赋值就叫做半缺省函数。不过需要注意的是,半缺省参数必须从右往左依次来给出,不能间隔着给,不然我们就没办法分辨出来哪个是赋值过的,而另外一个需要注意的是,缺省参数并不能在函数定义和声明中同时出现。
//a.h
void func(int a = 10);
//a.cpp
void func(int a = 20)
{
}//此时,声明与定义位置同时出现,但是两个位置提供的值并不相同,编译器无法分辨到底该使用哪一个缺省值
缺省值必须是全局变量或者常量,缺省参数在函数定义时被评估,而不是在函数调用时。如果缺省参数是一个非常量或局部变量,可能会引发内存管理或逻辑上的混乱。并且在C语言中无法使用缺省函数(编译器不支持)。
四、函数重载
函数重载是函数的一个特殊情况,在C++中允许在同一作用域中存在名字相同而参数(个数、类型、类型顺序)不同的函数存在,通常用来处理功能相似的数据不同的问题。我们来看一看下面的例子:
#include<iostream>
using namespace std;//参数类型不同
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;
}
//参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
//参数类型顺序不同
void func(int a, char b)
{
cout << "func(int a, char b)" << endl;
}
void func(char b, int a)
{
cout << "func(char b,int a)" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
func(10, 'a');
func('a', 10);
return 0;
}
我们自己看看理解一下就能知道何为重载,至于为什么C++可以重载而C并不支持重载我,我们可以去看其重载的原理--名字修饰(name Mangling)。
五、引用
1.引用的概念
引用并不是重新定义一个新的变量,而是对一个已存在变量取别名,比那一起并不会为引用变量开辟新空间,而是与被引用的变量共用一片空间。
好比我们每个人在小时候或多或少都会有绰号,我们的名字和绰号指的都是我们自己,不能说绰号就是另外一个人了,绰号和名字共用一个人。
void TestRef()
{
int a = 10;
int& ra = a;//定义引用类型
cout << "a=" << a;
cout << "ra=" << ra;
}
输出结果:
10
10
ra和a的值相同,如果我们将ra的值进行改变,我们会发现a的值也改变了,说明其两个是共通的。
2.引用的特性
我们知道,引用时给已经存在的变量取别名,于是我们可以知道,我们并不能直接取别名,毕竟你不能直接给一个空气取绰号吧,并且我们也不能将两个人取一个绰号,否则我们会分不清楚,所以我们有:
1. 引用在定义时必须初始化
2. 一个变量可以有多个引yi用
3. 引用一旦引用一个实体,再不能引用其他实体
void TestRef()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
这一段代码我们就可以看出来引用的特性。
3.常引用
引用并不能引用常量,因为给一个只认自己的原来的名字的人取绰号会生气哈哈。
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
4.引用的使用场景
我们直接来看代码示例:
做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
我们来看下面一段代码并思考一下他的结果与原因:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
5.传值与传引用的效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低,所以我们更加的腿脚使用传引用来提高效率。
我们来直观的以一段代码来看看差距:
#include<iostream>
#include <time.h>
using namespace std;struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}int main()
{
TestReturnByRefOrValue();
return 0;
}
这里我们可以看到两个返回的效率区别,引用返回会有显然的效率优势,我们的引用返回时间十分的短就约等于0了
6.引用与指针的区别
在很多人的认识中,引用和指针的本质是一样的,因而认为两个没有区别,但是事实上并不是。在语法概念上,引用就是一个别名,并没有独立的空间,和引用的实体共用一个空间。
int main()
{
int a = 10;
int& ra = a;
cout << "&a = " << &a << endl;
cout << "&ra = " << &ra << endl;
return 0;
}
我们看结果可以知道a与ra的地址是同一个地址,可以看出来引用产生的别名与被引用的变量共用一个地址。
那就真的没有空间了吗?nonono,嘻嘻,其实实质上也是用了空间的哦。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
我们对上面的一段代码进行反编译我们进行对比就可以看到,引用和指针本质上的调用时一模一样的,这也证实了引用时按指针的方式实现的。
引用和指针的不同点:
1.引用概念上定义的是一个变量别名,指针存储一个变量地址
2.引用创建时必须初始化,而指针并没有要求
3.引用在初始化时引用一个实体就不能在引用其他实体了,但是指针可以在任何时候指向任何一个同类型的实体
4.并没有NULL引用,但是有NULL指针
5.在sizeof中的含义不同,引用所得到的是引用类型的大小,指针始终是地址所占空间的字节个数
6.引用++就是实体往后加一,指针++是往后偏移一个类型
这就是初步内容啦,有问题记得在评论区问我哦