1. 命名空间
有的时候,当我们遇到变量重名时,无法解决使用哪个变量,这里我们引申命名空间来解决这个问题
a. 命名空间的定义
定义命名空间,需要使用到namespace关键字 ,后面跟命名空间的名字 ,然**后接一对{}**即可,{} 中即为命名空间的成员。
代码举例1
#include<iostream>
int a = 0;
int main()
{
int a = 10;
printf("%d\n",a);
printf("%d\n", ::a);// 在全局里面找 a 变量
}
一般来说,找寻变量,会先在局部里面找,如果局部里面没有,才会到全局里面找
代码举例2
namespace A
{
typedef struct book
{
int a;
}TB;
int d;
}
namespace B
{
typedef struct book
{
int b;
}TB;
}
namespace C
{
struct book
{
int c;
};
}
#include "test1.h"
#include "test2.h"
#include "test3.h"
#include<iostream>
using namespace std; //全局展开
using C::book;
using A::TB;
using A::d;
int main()
{
TB stu1;
B ::TB stu2;
struct C::book stu3;
cin >> stu1.a;
cin >> stu2.b;
cin >> stu3.c;
cin >> d;
cout << "hello bit" << endl;
cout << "hello bit" << '\n';
cout << "hello bit" << ' ';
cout << endl;
cout << stu1.a << endl;
cout << stu2.b << endl;
cout << stu3.c << endl;
cout << d << endl;
return 0;
}
b. 命名空间的使用的方式
- 加命名空间名称及作用域限定符
int main()
{
printf("%d\n", N::a);
return 0;
}
- 使用using将命名空间中某个成员引入
using N::b;
int main()
{
printf("%d\n", b);
return 0;
}
- 使用using namespace 命名空间名称 引入( 相当于将命名空间展开了)
using namespce N;
int main()
{
printf("%d\n", b);
return 0;
}
2. 输入与输出
代码举例1
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
代码举例2
#include<iostream>
using namespace std;
int main()
{
int arr[5];
for (int i = 0; i < 5; i++)
{
cin >> arr[i];
}
for (int i = 0; i < 5; i++)
{
cout << arr[i] << ' ';
}
return 0;
}
说明:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。 - cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
- <<是流插入运算符,>>是流提取运算符。
- 使用C输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C的输入输出可以自动识别变量类型。 - 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
3. 缺省参数
a. 缺省参数的概念:
缺省参数是声明或定义函数时 为函数的参数指定一个缺省值 。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
b. 缺省参数的分类:
- 全缺省参数
- 半缺省参数
全缺省参数:
代码举例1
#include<iostream>
using namespace std;
void ADD(int x = 0)
{
cout << x << endl;
}
int main()
{
ADD(10); // n = 10
ADD();
return 0; // n = 0
}
代码举例2
#include<iostream>
using namespace std;
void Mul(int x = 10 ,int y = 20) // 全缺省参数
{
cout << x << endl;
cout << y << endl;
}
半缺省参数:
代码举例3
void Mul(int x ,int y = 20) //半缺省参数
{
cout << x << endl;
cout << y << endl;
}
注意事项:
-
半缺省参数必须从右往左(连续的)依次来给出
-
缺省参数不能在函数声明和定义中同时出现**(在声明的时候给缺省参数)**
-
缺省值必须是常量或者全局变量
-
C语言不支持(编译器不支持)
代码举例4
#include<iostream>
using namespace std;
void ADD(int x = 0)
{
cout << x << endl;
}
void Sub(int x = 0, int y = 1)
{
cout << x << endl;
cout << y << endl;
}
void Mul(int x ,int y = 20)
{
cout << x << endl;
cout << y << endl;
}
int main()
{
ADD(10);
ADD();
Sub(10, 20);
Sub(10);
Mul(20);
return 0;
}
4. 函数重载
当遇到函数名相同的时候,c语言无法解决这个问题,而C++允许在同一作用域中 声明几个功能类似的同名函数 ,这些同名函数的形参列表 (参数个数 或 类型 或 类型顺序) 不同,常用来处理实现功能类似数据类型不同的问题。
举例
#include<iostream>
using namespace std;
void Add(int x, int y)
{
cout << x + y << endl;
}
void Add(double x, double y)
{
cout << x + y << endl;
}
void Add(int x)
{
cout << x << endl;
}
int main()
{
Add(1, 2);
Add(1.2, 2.0);
return 0;
}
原理:
本来是通过函数名来找到对应的函数地址,但在编译处理时,c++编译器会对函数名进行重新修饰,不同的编译器处理是不一样的
如:
gcc编译器会对 Add(int x,int y) 编译成:
_3Addii
注意事项:
如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
5. 引用
a. 引用的概念
引用 不是新定义一个变量,而是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间 。(给变量的别名再取一个别名也是可以的)
代码举例
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a; // b 和 a 是同一块空间的
int& c = b;
b++;
cout << a << endl;
cout << b << endl;
return 0;
}
运行结果:
b. 引用的实例
- 做参数
代码举例
#include<iostream>
using namespace std;
void Add(int &x, int &y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
Add(a, b);
cout << a << endl;
cout << b << endl;
return 0;
}
- 做返回值
代码举例1
#include<iostream>
using namespace std;
int& add()
{
static int c = 3;
c++;
return c;
}
int main()
{
int& d = add();
cout << d << endl;
add();
cout << d << endl;
return 0;
}
运行结果:
分析:
被 static 修饰的 c 生命周期发生了改变,由栈区存储到静态区。出了函数,并不会被销毁
返回的是 c 的别名(这样的好处是,避免了开辟空间。因为局部变量出了作用域会被销毁,所以返回值的时候,一定会先把值存到寄存器中,然后销毁函数栈帧,而直接返回变量的别名,就不需要存给寄存器了)
d 也是 c 的别名。c 改变,d也改变
代码举例2
#include<iostream>
using namespace std;
int& add(int& x)
{
return x;
}
int main()
{
int arr[5];
for (int i = 0; i < 5; i++)
{
add(arr[i]) = i * 10;
}
for (int i = 0; i < 5; i++)
{
cout << arr[i] << endl;
}
return 0;
}
运行结果:
分析:
x 是 arr[i] 的别名,返回的是 x 的别名,即arr[i]的别名
代码举例3
#include<iostream>
using namespace std;
struct a
{
int size;
int& add(int& x)
{
return x;
}
int capacity;
};
int main()
{
struct a c;
int arr[5];
for (int i = 0; i < 5; i++)
{
c.add(arr[i]) = i * 10;
}
for (int i = 0; i < 5; i++)
{
cout << arr[i] << endl;
}
return 0;
}
运行结果:
注意事项:
c++允许结构体的成员可以是一个函数
主要事项:
- 如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
- 引用多一个运用场景:结构体的成员可以是一个函数
(代码举例3)
- 引用类型 必须和引用实体 是同种类型的
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 用一旦引用一个实体,再不能引用其他实体
c. 常引用
面临三种可能:
- 权限的放大**(只有这个引用做不到)**(代码举例1,5)
- 权限的平移(代码举例2)
- 权限的缩小(代码举例3,4)
代码举例1
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
int& b = a; //权限的放大
return 0;
}
分析:
代码举例2
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a; // 权限的平移
return 0;
}
代码举例3
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& b = a; //权限的缩小
return 0;
}
代码举例4
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& b = a;
a++;
cout << a << endl;
cout << b << endl;
return 0;
}
运行结果:
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& b = a;
b++;
cout << a << endl;
cout << b << endl;
return 0;
}
代码举例5
#include<iostream>
using namespace std;
int main()
{
int a = 10;
double& i = a;
return 0;
}
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const double& i = a;
return 0;
}
分析:
这是一种强制类型转换 ,实际上,在转换的时候,会创建一个临时变量 (临时变量是已经转换过类型了,而原变量的类型不会因此而改变),临时变量具有常性,不能被修改 ,而引用拿到的别名是创建的临时变量 ,第一种写法实质上是扩大了权限
d. 值和引用的作为返回值类型的性能比较
我们提过一次,在可以用引用做返回值的前提下(注意事项第一条),引用的性能比值返回会更好( b. 代码举例1)
e. 引用和指针的区别
引用和指针的不同点:
-
引用概念上定义一个变量的别名,指针存储一个变量地址。
-
引用 在定义时必须初始化,指针没有要求
-
引用 在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体 (所以链表的构建中:next 只能是下一个节点的地址,而不能是下一个节点的别名 [链表的删除会出大问题] )
-
没有 NULL 引用,但有NULL指针
-
在sizeof中含义不同 :引用 结果为引用类型的大小 ,但指针 始终是地址空间所占字节个数(4 或 8)
-
引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
-
有多级指针,但是没有多级引用
-
访问实体方式不同,指针需要显式解引用,引用编译器自己处理
-
引用比指针使用起来相对更安全
6. 内联函数
将内联函数先简单提一个知识点:宏
a. 宏的复习
复习
宏的坏处:
- 不方便调试宏。(因为预编译阶段进行了替换)
- 导致代码可读性差,可维护性差,容易误用。
- 没有类型安全的检查 。
宏函数:
代码举例(写一个Add的宏函数)
#define Add(int x,int y) return x + y
#define Add(x,y) x + y
int main()
{
Add(1,3) * 3; // 1 + 3 * 3
return 0;
}
#define Add(x,y) (x + y)
int main()
{
Add(1 | 3,3 & 4); // (1 | 3 + 3 & 4) 但是 + 的优先级高于 | 和 &
return 0;
}
#define Add(x,y) ( (x) + (y) );
int main()
{
Add(1,3) * 3; // ((1) + (3)); * 3
return 0;
}
#define Add(x,y) ((x) + (y))
宏的好处:
- 增强代码的复用性。
- 提高性能。
替代宏的一些方式
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
b. 内敛函数概念
以inline修饰 的函数叫做内联函数,编译时 C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。(相当于借用了宏函数的一些优点)
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
cout << Add(3, 5) << endl;
return 0;
}
c. 特性
-
inline是一种以空间换时间 的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
-
inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同。
一般建议:将函数规模较小 (即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
7. 关键词 auto
auto可以自己推到对象的类型
代码举例
#include<iostream>
using namespace std;
int main()
{
int a = 10;
auto b = a;
return 0;
}
注意事项:
- auto不能直接用来声明数组
错误代码举例
#include<iostream>
using namespace std;
int main()
{
auto arr[] = { 1,2,3,5 };
return 0;
}
- auto不能作为函数的参数
错误代码举例
#include<iostream>
using namespace std;
int ADD(auto x, int y)
{
return x + y;
}
int main()
{
int a = 4;
int b = 5;
ADD(a, b);
return 0;
}
- auto声明同时初始化
错误代码举例
#include<iostream>
using namespace std;
int main()
{
auto b;
return 0;
}
auto的使用规则
- auto与指针和引用结合起来使用用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
代码举例
#include<iostream>
using namespace std;
int main()
{
int a = 4;
auto b= &a;
auto* c = &a;
cout << b << endl;
cout << c << endl;
return 0;
}
运行结果:
#include<iostream>
using namespace std;
int main()
{
int a = 4;
auto* c = a;
cout << c << endl;
return 0;
}
- 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错(因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。)
代码举例
#include<iostream>
using namespace std;
int main()
{
auto a = 4, b = 5;
cout << a << ' '<< b << endl;
return 0;
}
运行结果:
#include<iostream>
using namespace std;
int main()
{
auto a = 4, b = 5.0;
cout << a << ' '<< b << endl;
return 0;
}
- 打印函数类型
代码举例(VS2022,X86)
#include<iostream>
using namespace std;
int main()
{
auto a = 4;
auto * b = &a;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
return 0;
}
运行结果:
- for循环遍历数组(一个个取出数组元素并且一个临时变量)
代码举例1
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1,3,4,3,5 };
for (auto i : arr) //打印数组元素
{
cout << i << endl;
}
return 0;
}
运行结果:
取出数组的每一个元素,并且赋值给临时变量 i
代码举例2
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1,3,4,3,5 };
for (auto& i : arr) //修改数组元素
{
i *= 2;
}
for (auto j : arr) //打印数组元素
{
cout << j << endl;
}
return 0;
}
运行结果:
i , j 都是临时变量
for循环后的括号由冒号" :"分为两部分:第一部分是范
围内用于迭代的变量,第二部分则表示被迭代的范围
代码举例3
#include<iostream>
using namespace std;
void Print(int arr[])
{
for (auto i : arr)
{
cout << arr;
}
}
int main()
{
int arr[] = { 1,3,4,3,5 };
Print(arr);
return 0;
}
无法确定 arr的范围(传过来的只是首元素的地址)
8. 指针空值 nullptr
nullptr 与 NULL 的区别
NULL实际是一个宏,在传统的C头文件(stddef.h)中
所以:它可能代表 整型 0 或者 (无类型的地址)0
而 nullptr 只有一种含义 :
(无类型的地址)0
代码举例
注意事项:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。