本篇博客默认浏览者拥有C语言基础
目录
缺省函数:
函数重载 :
引用 :
内联函数:
命名空间
在C语言中,定义一个变量时有几率会与头文件中的已存在的变量重命名,导致出现重赋值的操作导致和错误,所以在C++中,我们可以引用命名空间来进行区分,下面是示例代码
#include <iostream>
namespace mynp//自定义一个mynp命名空间
{
int rand = 10;//此处定义一个rand变量,赋值为10
int Sum(int rand)//此处定义一个Sum求和函数,参数为rand
{
return rand + rand;
}
}
int Sum()//命名空间外定义一个同名函数Sum
{
return 0;
}
using namespace mynp;//调用命名空间
int main()
{
int rand = 100;
printf("此处为命名空间外的rand:%d\n", rand);
printf("此处为命名空间内的rand:%d\n", mynp::rand);
printf("此处调用了命名空间外的函数Sum:%d\n", Sum());
printf("此处调用了命名空间内的函数Sum:%d\n", mynp::Sum(rand));
}
运行结果:
通过上面的代码可以看到,我们可以定义一个命名空间,在内部定义我们想要的变量和方法
当我们在命名空间外定义了同名的变量与函数后并不会报错
但是需要注意的是 命名空间的定义方式 与 成员引入方法
namespace <命名空间名字>
{
在此处定义变量与方法
int A = 1;
int Sum
{
return 0;
}
...
}
using namespace <命名空间名字>;
int main()
{
int A = 10;
int B = A; //使用命名空间外的A
int C = <命名空间名字>::A; //使用命名空间内的A
}
输入与输出
在C语言中我们总是需要使用scanf与printf来进行输入与输出的操作
在输入与输出时还需要专门进行类型的分辨操作,显得过于累赘
在C++中引用了一种全新的方法进行输入输出操作
cin cout
下面是代码示例
#include <iostream> //std命名空间所在头文件
using namespace std; //std包含了cout与cin的定义
int main()
{
int a = 10;
int b;
cout << "请输入b的值:";
cin >> b;
cout << "a的值为:" << a << endl;
int c, d;
cout << "请输入c和d的值:" << endl;
cin >> c >> d;
cout << a << " " << b << " " << c << " " << d << endl;
}
运行结果如下:
需要注意的是:
1.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
3. >是流提取运算符
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式, C++的输入输出可以自动识别变量类型
5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识, 此知识点后续再谈~
我们除了这样使用,也可以选择不添加 using namespace std;来使用cin与cout
#include <iostream>
//此处使用std::cin与std::cout的方法使用
int main()
{
int a = 10;
int b;
std::cout << "请输入b的值:";
std::cin >> b;
std::cout << "a的值为:" << a << endl;
int c, d;
std::cout << "请输入c和d的值:" << endl;
std::cin >> c >> d;
std::cout << a << " " << b << " " << c << " " << d << endl;
}
缺省函数
理解缺省函数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值
在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参
举个例子:
#include <iostream>
using namespace std;
void test(int a = 0) //此处定义一个测试函数
{ //参数设定一个默认值
cout << a << endl; //输出传入的参数
}
int main()
{
test(); //此处不传参调用测试函数
test(10); //测出传入10作为参数调用测试函数
}
运行结果:
可以看到,我们在定义函数时就设定了参数的默认值为0
在下面进行调用时,不传参数的函数输出了默认值0
而传入10作为参数的函数输出了10
这便是缺省函数的使用方法
缺省函数的分类
全缺省函数
void test(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
test();
test(100, 200, 300);
}
运行结果:
此处定义一个测试函数,存在3个参数abc,并都设置了默认值,这就叫全缺省函数
半缺省函数
void test(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
test(100, 300);
test(100, 200, 300);
}
运行结果:
此处定义一个函数拥有3个参数abc,其中bc给定默认值,而a没有给定,这就叫做半缺省函数
值得一提的是:
void test(int a, int b = 10, int c)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
test(100, 300);
test(100, 200, 300);
}
对于这段代码,a与c未给定默认值,下方第一次调用会出现错误
原因是c未接收到参数,此处的100与300分别给的是a与b,所以在后续的使用需要注意缺省函数的填写顺序,以免出现类似错误
函数重载
理解函数重载
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数
这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题
举例理解:
#include<iostream>
using namespace std;
// 1、参数类型不同
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;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
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()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
通过上面可以看到,即使函数重名,但只要参数类型,参数个数与参数顺序不同,都可以构成函数重载进行使用
值得一提的是
cin与cout底层是通过多个函数重载操作来实现cin与cout的忽略数据类型的匹配
Qs:为什么C语言不支持函数重载而C++支持?
As:一句话概括
C语言的编译方式是通过同名函数进行链接,没办法区分
C++是通过函数修饰规则来区分链接,只要参数不同,修饰出来的名字就不一样,就支持了重载
引用
理解引用
引用不是新定义一个变量,而是给已存在变量取了一个别名
编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
下面举个例子来进行理解:
int main()
{
int a = 100;
int& aa = a; //&引用关健符
cout << "这里为被引用变量:" << a << endl;
cout << "这里为引用变量:" << aa << endl;
}
运行结果:
引用的特征
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
4.引用对象不能为常量
5.引用变量的数据类型必须和被引用对象的数据类型一致
int main()
{
int a = 100;
int& aa = a;
int& bb; //引用必须初始化
int& aaa = a; //一个变量可以被多个引用变量引用
int c = 200;
int& aa = c; //引用不能进行多次初始化操作
const int d = 300;
int& dd = d; //引用对象不能为常量
double e = 400.00;
int& ee = e; //引用变量的数据类型必须一致
cout << "这里为被引用变量:" << a << endl;
cout << "这里为引用变量:" << aa << endl;
}
引用的用法
作为函数的参数
将 C++ 引用作为函数参数有以下几个好处:
-
避免拷贝:引用参数允许函数直接访问并操作原始数据,而不需要进行拷贝操作。这可以提高程序的性能,尤其是当传递大型对象时。
-
修改调用者的数据:通过使用引用参数,函数可以直接修改调用者传递的数据。这对于需要在函数内部修改外部变量的情况非常有用。
-
更直观的语法:使用引用参数可以让函数调用的语法更加直观和自然。调用者无需使用额外的语法来传递指针或对象副本,只需将变量作为参数传递即可。
-
实现函数重载:函数重载是 C++ 的一项强大特性。通过使用引用参数,可以在函数签名中使用相同的参数类型,但实际上根据引用的类型选择不同的重载函数。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
需要注意的是,传递引用参数可能存在一些潜在的风险。例如,如果函数不需要修改传递的数据,应该使用常量引用来避免意外修改。此外,如果引用参数指向的对象在函数执行期间被销毁,会导致悬空引用的问题。因此,在使用引用参数时需要确保引用的有效性和生命周期。
作为函数的返回值
将 C++ 引用作为函数返回值有以下几个好处
-
避免拷贝:返回引用可以避免大型对象的拷贝操作,减少函数返回值的开销。
-
可链式操作:返回引用使得函数调用可以使用连续的点运算符进行链式操作。这样的语法更加简洁和直观,提高了代码的可读性。
-
支持函数重载:通过使用引用返回值,可以在函数签名中使用相同的返回类型,但根据不同的输入参数选择不同的重载函数。这是 C++ 函数重载的一个重要应用场景。
-
可以实现赋值操作的连续性:当返回引用时,可以将函数调用的结果直接赋值给变量,从而实现对返回值的连续操作。这在一些表达式操作和流式编程中非常有用。
需要注意的是,返回引用时需要确保引用指向的对象在函数返回后仍然有效。如果引用指向的对象在函数结束后销毁,则返回悬空引用会导致未定义行为,下面看一个例子:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = Add(1, 2);
cout << "Add(1, 2) is :" << ret << endl;
}
这个代码的结果如下:
但是我们知道
在Add函数内部创建临时变量int c,函数返回值为c的引用,临时变量c出了函数栈帧销毁,这个返回值怎么还能对c进行引用???
其实这是就是编译器的未定义行为
对于Visual Studio而言,使用的默认编译器是 Microsoft Visual C++ 编译器(MSVC)
在这个编译器内,该函数栈帧结束后销毁,但是由于没有其他函数对这个函数栈帧进行占用操作,使得任然保留了这个值,但这种行为是有风险,我们可以在Linux系统上使用g++编译看看:
可以看到下方报了警告 warning: reference to local variable ‘c’ returned [-Wreturn-local-addr]
所以对于这种行为,在每一种编译器下都有不同的结果,需要谨慎使用
引用和指针的区别
-
语法和操作:
- 引用使用
&
符号进行声明,指向对象后无需使用符号进行间接访问,直接使用对象名即可。例如:int a = 5; int& ref = a; ref = 10;
- 指针使用
*
符号进行声明,通过解引用操作符*
访问指针指向的对象。例如:int a = 5; int* ptr = &a; *ptr = 10;
- 引用使用
-
空值:
- 引用必须在声明时被初始化,并且不能指向空值(nullptr)。
- 指针可以在声明时不进行初始化,允许指向空值(nullptr)。
-
重新赋值:
- 引用一旦被赋值,不能再引用其他对象。引用在初始化后,就一直引用该对象,不能更改引用的目标对象。
- 指针可以重新赋值,可以在运行时改变指针指向的对象。
-
引用的安全性:
- 引用在使用时不需要进行空指针检查,因为引用必须在初始化时指向有效对象。
- 指针需要进行空指针检查,避免解引用空指针导致的错误。
-
数组和函数的使用:
- 引用无法直接指向数组或函数,必须指向某个对象。
- 指针可以指向数组或函数,并可以进行数组指针或函数指针的运算。
但实际上,对于引用而言
底层逻辑上可以被视为指针的一种语法糖。在编译器内部,引用通常被实现为指针的别名
通过指针来实现引用的语义
当我们声明一个引用时,编译器会将其转换为一个指针,并与所引用的对象关联起来
在访问引用时,编译器会自动解引用指针,可以直接操作引用所指向的对象,就像操作该对象的直接别名一样
我们可以通过下面的一个例子来理解:
int main()
{
int a = 10;
int b = 20;
int& aa = a;
int* pb = &b;
}
这里定义了两个变量a和b,变量a使用aa进行引用,变量b使用pb进行指针指向
在反汇编内他们的实现是这样的:
可以看到,引用与指针都是使用ptr实现的
所以引用在底层实现上实际是有空间的,因为是按照指针方式来实现的
内联函数
理解内联函数
以inline修饰的函数叫做内联函数
编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销
内联函数提升程序运行的效率
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(5, 3); // 内联函数在这里直接展开,不会产生函数调用
return 0;
}
inline是一种以空间换时间的做法
如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用
缺陷:可能会使目标文件变大
优势:少了调用开销,提高程序运行效率
需要注意的是:
内联函数的展开是由编译器决定的,因此 inline 关键字只是对编译器的提示,不一定会被采纳
编译器通常会根据函数的复杂度、调用频率和编译器策略等因素来决定是否将函数进行内联展开