1.命名空间域——namespace
——>简单功能介绍:
我们会有时会遇到以下代码:
#include<stdio.h>
int a = 0;
namespace num
{
int a = 1;
}
int main()
{
int a = 2;
printf("%d\n", a);
return 0;
}
这些代码当然是正确的,三个整形的名字相同也可以吗?因为他们在不同的范围内,所以当然是可以的,在一个范围内名字相同,这是不可以的。
我们如果运行程序会发现,输出的是0,可以看出是有一定的顺序的——访问一个变量,顺序是局部域->全局域->展开了的命名空间域或者指定访问的命名空间域。在图中代码中,num相当于是一个命名空间域的名字,namspace则是命名空间域的关键字。
对于上面说到的展开的命名空间域,演示如下:
在namespace num前面加上using后,相当于打开了这个命名空间域,那么这时候再去printf会发现一个问题,这个a发生了错误,那么我们可以得到展开了的命名空间域就类似于原本空间域里面的a暴露在了和全局变量一样的全局域里面。所以不要轻易的使用using。展开命名空间指的是编译时编译器是否会到里面去搜索。
这个关键字使用来解决命名冲突的问题的,给出以下代码:
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
printf("%d\n",rand);
return 0;
}
乍一看下面的代码没有什么问题但是运行的时候就会报错,这是因为rand和stdlib头文件里面的一个函数名重合了,所以会发生这样的错误。正确的方式避免这种情况就是使用namespace。
把这个变量放在一个域里面,然后用::来找到它,::就是所谓的域作用限定域。::在这个符号左边加上命名空间域的名字,就可以找到里面的变量,如果左边是空白的没加东西,那么就会找到名字为a的全局变量。
——>命名空间的定义:
(1) 命名空间内可以定义变量,函数和类型等。
(2)命名空间可以嵌套使用。
注:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都受限于该命名空间中。
——>命名空间的使用:
命名空间有三种使用方法:
(1)加命名空间域名称以及作用域限定符:
int main()
{
printf("%d\n", N::a );
return 0;
}
这样就可以正确的使用到N这个命名域里面的a变量了。
(2)使用using namespace来引入变量:
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
using namespace N;
int main()
{
printf("%d\n", b);
return 0;
}
打开这个作用域后就可以直接使用了,但是在一些大型项目里面是不建议这样使用的。最好的方式应该是下一种:
(3)使用using将命名空间中某个成员引入:
using N::b;
int main()
{
printf("%d\n", b);
return 0;
}
这样在打代码的过程中就可以把只把常用的东西给“放出来”。
2.c++的输出和输入
——>书写hello world:
我应该如何用c++写一个hello world呢:
#include<iostream>
//std是c++标准库的命名空间名,c++将标准库的定义实现都放在这个命名空间里面。
using namespace std;
int main()
{
cout<<"hello world!"<<end1;
return 0;
}
说明:
- 先包含头文件再使用
using namespace std;
:这是常见的做法。先包含头文件,编译器能知道有哪些标识符可用,接着使用using namespace std;
让这些标识符在当前作用域直接使用。 - 先使用
using namespace std;
再包含头文件:这种顺序也可行。不过在包含头文件之前,由于没有引入具体的标识符,using namespace std;
暂时没有实际作用。只有在包含头文件之后,其中的标识符才会被引入到当前作用域。
——>std的使用惯例:
3.缺省值
——>缺省值的概念:
#include<iostream>
using namespace std;
void fun(int a = 0)
{
cout<<a<<endl;
}
int main()
{
fun();//使用缺省值
fun(10);//使用给定的值
return 0;
}
上述代码就是缺省值的一个具体使用案例,缺省值就相当于预先给定好的一个值。
——>缺省值的类型:
(1)全缺省值:
void add(int a = 5, int b = 10, int c = 20)
{
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<end1;
}
如上面代码所示全缺省值就相当于所有的形参都给上了预定的值。
(2)半缺省值:
void add(int a, int b = 10, int c = 20)
{
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<end1;
}
在上述代码中,形式参数a并没有给定预定的参数,这种形式就叫做半缺省值。
注意:(1)在全缺省类型中,你要调用add函数,如果你以此输入了自己想要的两个数字,那么就会按照顺序从左到右覆盖缺省值去使用。中间不能隔着给数字,没有这种定义。
(2)在半缺省中,所给定的数字是从右往左给出,不能隔着给。
——>使用时的注意事项:
如果在多文件中使用缺省值的话,只需要在函数的声明的时候给出缺省值。如果在声明和定义使用缺省值的话会报错。
//Stack.h文件
#include<iostream>
typedef struct stack
{
int* a;
int top;
int capacity;
}stack;
void stackinit(stack* pst ,int defaultcapacity = 40);
stack.cpp文件:
#include"Stack.h"
void stackinit(stack* pst,int defaultcapacity)
{
pst->a = (int*)malloc(sizeof(int)*defaultcapacity);
if(a == NULL)
{
perror("malloc fail");
return;
}
pst->top = 0;
pst->capacity = defaultcapacity;
}
在函数声明时给出缺省值就行了,在定义时不需要写出缺省值的大小。
4.函数重载:
函数重载:是函数的一种特殊情况,c++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或参数顺序不同)。常用来处理实现功能类似数据类型不同的函数。
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
int main() {
cout << Add(1, 2) << endl;
cout << Add(1.1, 3.3) << endl;
return 0;
}
5.引用:
引用的概念,就像当于取别名,别名和原本的名字指向同一块空间,我们学习的时候就当做引用时不开辟空间的。
int a = 10;
int& b = a;
int& c = a;
int& d = b;
在上述代码中,bcd均是a的别名,改变bcd的值会改变a
注意:(1)引用类型必须和引用实体是同种类型的。
(2)引用在定义时必须初始化
(3)一个变量可以有多个引用
(4)引用一旦引用一个实体,就不可以再引用其他实体了。
(1)引用做参数:
使用场景,可以在函数里面使用:假设我们要写一个交换函数。
swap(int& a , int& b)
{
int tmp = b;
b = a;
a = tmp;
}
我们使用引用,就不必要和指针一样麻烦,可以直接对对象进行操作。
(2)引用作为返回值:
下面以add函数为例:
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;
}
注意:引用的使用会涉及权限问题:
(1)权限放大:
const int a = 0;
int& b = a;
通俗一点的解释就是变量a不能修改,但是b作为a的别名,它却有了可以更改的权力。
(2)权限可以缩小或者平移:
int x = 0;
int& y = x;
const int& z = x;
原本的变量可以修改,别名不能修改原本的值,这就是一种权限的缩小。
(3)以下代码是值得注意的:
double d = 1.11;
int c = d;
int& r = d;
上述代码在编译器里面输入会发现是明显错误的,上述的代码存在隐式转换。用int类型的r去作为一个double类型的别名,会出现一个d的int类型的临时变量,转换过程都存在这种变量,这种临时变量具有常性,所以r存在权限放大的问题,是不对的,这段代码的最后一行变为const int& r = d;
就是一个正确的代码。
(4)在函数使用中的问题:
int func()
{
static x = 0;
return x;
}
int main()
{
int& ret = func();
}
在函数 返回时,返回的是一个和(3)中一样具有常性的临时变量,所以这里存在和(3)中一样的问题。
常规写法和使用引用的对比举例:
常规顺序表(静态):
#include <stdio.h>
#include <assert.h>
typedef struct SeqList
{
int a[100];
int size;
} SeqList;
// 找到pos位置的值是什么
int sltfind(SeqList* ps, int pos)
{
assert(ps != NULL);
return ps->a[pos];
}
// 修改pos位置的值,变成x
void sltmodify(SeqList* ps, int pos, int x)
{
assert(pos < 100 && ps != NULL);
ps->a[pos] = x;
}
int main()
{
return 0;
}
使用引用改进这段顺序表:
#include<iostream>
#include<cassert>
typedef struct SeqList
{
int a[100];
int size;
}SeqList;
//找到pos位置的值
int& SLAt(SeqList* ps,int pos)
{
assert(pos>=0 && pos<ps->size && ps!=NULL);
return ps->a[pos];
}
int main()
{
SeqList sl;
sl.size = 3;
sl.a[0] = 1;
sl.a[1] = 2;
sl.a[2] = 3;
// 测试获取指定位置的值
int value = SLAt(&sl, 1);
std::cout << "位置 1 的值是: " << value << std::endl;
// 测试修改指定位置的值
SLAt(&sl, 1) = 10;
std::cout << "修改后位置 1 的值是: " << sl.a[1] << std::endl;
return 0;
return 0;
}
总结:使用引用做返回值,我们会发现不仅减少了拷贝,还可以修改返回值+获取返回值。
6.内联函数:
——>内联函数的特点:
(3)inline函数不建议声明和定义分开,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接的时候会找不到。