思维导图大纲:
namespac命名空间
- 什么是namespace命名空间
- namespace命名空间有什么用
什么是命名空间
namespace命名空间是一种域,它可以将内部的成员隔绝起来。举个例子,我们都知道有全局变量和局部变量,全局变量存在于全局域中,局部变量存在于局部域中,看下段代码 ,根据已经学习的知识我们都知道局部优先,所以输出会打印局部变量的数据。
#include <stdio.h>
// 全局域
int a = 10;
int main()
{
// 局部域
int a = 0;
printf("%d", a);
}
由上述代码可以知道,不同的域可以存在相同名称的变量,而输出函数会优先在最近的域中寻找变量a,如果找不到才去全局找。那么有没有一种办法可以绕过局部域,打印全局域的变量a呢,答案是声明我们要打印的变量来源。
#include <stdio.h>
// 全局域
int a = 10;
int main()
{
// 局部域
int a = 0;
printf("%d\n", a);
// 在变量名称前加上:: 这样编译器就知道到全局域中寻找了
printf("%d\n", ::a);
}
通过以上我们大概了解了域方面的知识,那么什么是namespac命名空间呢,简单的说就是一个我们可以自定义的域,在这个域中我们可以存放函数/变量等等,同时因为具有域的特性,我们在多人合作项目时,可以各种取一个命名空间,这样我们可以使用相同命名的函数或者变量了 。
namespace关键字,后面的ouyang1可以任意取名:
#include <stdio.h>
// 命名空间
namespace ouyang1
{
int a = 15;
}
namespace ouyang2
{
int a = 20;
}
int main()
{
printf("%d\n", ouyang1::a);
printf("%d\n", ouyang2::a);
}
当然在你需要使用什么内部成员时声明一下就可以了 ,以下时使用不同成员声明的代码表述。
namespace ouyang
{
// 变量
int a = 10;
// 函数
int add(int x, int y)
{
return x + y;
}
// 结构体
struct student_name
{
char name[20];
};
// 枚举
enum
{
red = 1,
blue,
yellow
};
}
int main()
{
printf("%d\n", ouyang::a);
printf("%d\n", ouyang::add(1, 3));
struct ouyang::student_name s1;
printf("%d\n", ouyang::red);
printf("%d\n", ouyang::blue);
printf("%d\n", ouyang::yellow);
}
命名空间的作用:
命名空间可以防止命名冲突的发生,同时每一个命名空间都相当于一个域,访问需要声明获得权限,简单说就相当于我们所划定的领域,这样的设计可以使我们在团队项目中避免冲突发生。
命名空间的展开:
那么我们每次去使用命名空间时,都需要在前声明会不会有点麻烦,为了解决这一问题,c++语法中还包含了命名空间的展开,展开代码如下:
// 命名空间
namespace ouyang
{
int a = 10;
}
// 命名空间的展开
using namespace ouyang;
展开命名空间,就相当于对编译器开放了该命名空间的访问权限,但是注意域的访问顺序还是存在先后的,首先编译器还是会到局部域中去寻找,其次才是全局域和命名空间域。
这种展开有好也有坏,好处是:我们可以随意访问命名空间内的成员,坏的一方面如果命名空间展开的成员过多,不可避免会产生命名冲突问题,如以下代码:
// 命名空间
namespace ouyang
{
int a = 10;
}
// 命名空间的展开
using namespace ouyang;
int a = 0;
int main()
{
printf("%d", a);
return 0;
}
由于展开命名空间后,编译器访问全局域和命名空间域的优先程度一致,所以编译器无法区分究竟因该访问哪一个。
命名空间的局部展开:
为了防止展开太多出现以上情况,c++语法中又提出了局部展开,就是展开到特定的成员,这样可以大大降低了命名冲突带来的报错,也方便快速查找问题。
局部展开代码展示:
// 命名空间
namespace ouyang
{
int a = 10;
int b = 5;
}
// 命名空间的局部展开
using ouyang::b;
int main()
{
printf("%d", b);
return 0;
}
有了局部展开后,我们以后想访问命名空间的谁再去展开,这样既可以优化代码的冗长度,又可以很好的控制使用。
C++的输入输出
- cout
- cin
- endl
前提:我们都知道c语言中使用输入输出函数scanf/printf需要包括头文件#include<stdio.h>,同样c++语法中使用以上的函数也需要包含头文件#include <iostream>。
cout输出符
上一个知识点我们了解了命名空间,c++在使用头文件时,为了避免命名冲突,也给其中的成员创建了命名空间域用于保护,如我们要使用的输入输出等等的函数都包含着命名空间std中。
注意:c++是在c语言的基础上衍生的,所以可以适用c语言的语法
代码使用方式:
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
// 变量
int a = 10;
double b = 13.14;
char c = 'a';
// c语言的输入输出
printf("C语言:");
printf("%d %lf %c\n", a, b, c);
// c++语言的输入输出
// << 这个符号叫做流插入 / << 还有另外的一层意思就是左位移操作符
// cout会自动识别类型输出
cout << "C++: ";
cout << a << " " << b << " " << c << '\n';
}
cin输入符
cin输入符可以制动识别输入的数据类型存放到相应的变量之中。
代码演示:
int main()
{
// 变量
int a = 0;
double b = 0;
char c = 0;
// 流提取
cin >> a >> b >> c;
cout << a << " " << b << " " << c <<endl;
}
注:endl符相当于换行
函数重载:
定义:在同一个域中,可以存在相同命名的函数,但是这些函数需要满足以下条件之一:
- 1.参数的类型不同
- 2.参数的个数不同
- 3.参数的顺序不同
代码演示:
// 函数重载
// 全局域中,交换函数Swap
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void Swap(double* a, double* b)
{
double tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int num1 = 1, num2 = 2;
double num3 = 1.1, num4 = 2.2;
cout << "交换前:" << endl;
cout << num1 << " " << num2 << " " << num3 << " " << num4 << endl;
// 交换
Swap(&num1, &num2);
Swap(&num3, &num4);
cout << "交换后:" << endl;
cout << num1 << " " << num2 << " " << num3 << " " << num4 << endl;
return 0;
}
以上是函数重载中的参数类型不同,以下还有参数数量不同,参数顺序不同:
// 参数数量不同
void print(int a, double b, char c)
{
cout << "print(int a, double b, char c)" << endl;
}
void print(int a, double b)
{
cout << "print(int a, double b)" << endl;
}
int main()
{
print(1, 1.1, 'a');
print(1, 1.1);
return 0;
}
// 函数重载
// 参数顺序不同
void swap(int* a, double* b)
{
cout << "swap(int* a, double* b)" << endl;
}
void swap(double* a, int* b)
{
cout << "swap(double* a, int* b)" << endl;
}
int main()
{
int num1 = 1;
double num2 = 1.1;
swap(&num1, &num2);
swap(&num2, &num1);
return 0;
}
引用
引用符号->&
引用的定义:引用相当于给变量取一个别名,就像张三,我们平日可能会叫他老张,张三,也可能会叫小张,但这些所有的称呼都指向一个人张三,引用的作用就是这样:
// 引用
int main()
{
int a = 0;
int& b = a;
b++;
cout << "a=" << a << " " << "b=" << b << endl;
return 0;
}
以上代码我们可以看见,我们给变量a取了一个别名b,我们在使用b++的时候就相当于a++,所有最后的结果a=1。
权限放大和权限缩小问题
我们在给变量取别名的时候,可以通过别名对变量进行修改,但如果我们的变量由const修饰之后怎么办呢,这就涉及到权限放大和权限缩小问题
1. 权限可以缩小,不可以放大
int main()
{
// 权限放大不被允许
const int a = 10;
// int& b = a; // err
// 权限缩小允许
int a = 10;
const int& b = a;
}
2. 拥有常量属性的变量权限不可以放大。
如以下代码:
int main()
{
int x = 1, y = 2;
// int& c = x + y; // x+y这种临时变量具有常性 // err
const int& c = x + y;
return 0;
}
应用场景
- 做参数
- 做返回值
做参数可以在交换函数中替代指针的适用。
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 2;
cout << a << " " << b << endl;
Swap(a, b);
cout << a << " " << b << endl;
}
做返回值可以用于接收结果。
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. 引用在语法上不开空间,底层和指针一样需要开空间,但是我们应用层面默认为不开空间
- 2. 引用必须初始化,而指针可以不用,或者指向空
- 3.引用在初始化一个实体后,不能在改成别的实体,但是指针可以改变指向
- 4.引用自加是实体加1,而指针加1,是指向的类型位置+1
- 5.有多级指针,没有多级引用
- 6.指针的大小由sizeof计算是固定的,区别是64位机器还是32位机器,但是引用的大小与引用的实体相关,比如说:引用的类型int,大小4字节;引用的类型double,大小8字节
- 7.引用比指针安全
- 8.访问方式上,指针需要解引用,但是引用不需要,编译器会自己处理
缺省参数
我们往往有时候在使用函数时会忘记传入参数,那么有没有一种办法在我们没有传参的情况下,函数可以使用默认的值进行传入使用呢?这就是缺省参数。
// 全缺省
// 传值按传值来,不传值按默认来
void print1(int a = 1, int b = 1, int c = 1)
{
cout << a << " " << b << " " << c << endl;
}
// 半缺省 -> 没有缺省的参数必须传值
void print2(int a, int b = 1, int c = 1)
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
print1();
print1(10);
print1(10, 20);
print1(10, 20, 30);
print2(10);
print1(10, 20);
print1(10, 20, 30);
return 0;
}
注意: 缺省只能从右往左缺省,缺省参数不可以跳跃传参!
(不允许)跳跃传参的演示如下:
// 传值按传值来,不传值按默认来
void print1(int a = 1, int b = 1, int c = 1)
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
print1(, 10, );// err
return 0;
}
内联函数
定义:和c语言中的宏类似,被修饰的函数不会去使用地址调用,只会在原地展开,这种内联函数只适用于代码量少,简单的函数,并且原地展开的请求只是对编译器的建议,如果函数过大,编译器可以默认不执行展开改为调用,同时由于是原地展开,内联函数无法声明与定义分离,会在链接时报错找不到函数的地址。
// 内联函数
inline int add(int x, int y)
{
return x + y;
}
int main()
{
int ret = add(1, 2);
cout << ret << endl;
return 0;
}
auto关键字
定义:自动识别变量类型
使用方式如下:
// auto 关键字
int main()
{
auto a = 10;
auto b = 13.14;
auto c = 'a';
cout << a << " " << b << " " << c << endl;
return 0;
}
打印类型的函数typeid(变量名).name()
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
范围for
代码展示如下:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
for (auto x : arr)
{
cout << x << " ";
}
// 如果要改变arr中的值,使用&
for (auto& x : arr)
{
x *= 2;
}
cout << endl;
for (auto x : arr)
{
cout << x << " ";
}
}
空指针nullptr
在c语言中我们已经了解了空指针NULL,但是在c++语法中实现时,却存在一个坑,c++语法中的NULL 定义为0 ,而不是(void*)0 ,就会导致以下代码情况。
void pint(int)
{
cout << "void pint(int)" << endl;
}
void pint(int*)
{
cout << "void pint(int*)" << endl;
}
int main()
{
int i = 0;
pint(i);
pint(NULL);
pint(nullptr);
}
注意:
- 1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。