C++基础

本篇博客默认浏览者拥有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++ 引用作为函数参数有以下几个好处:

  1. 避免拷贝:引用参数允许函数直接访问并操作原始数据,而不需要进行拷贝操作。这可以提高程序的性能,尤其是当传递大型对象时。

  2. 修改调用者的数据:通过使用引用参数,函数可以直接修改调用者传递的数据。这对于需要在函数内部修改外部变量的情况非常有用。

  3. 更直观的语法:使用引用参数可以让函数调用的语法更加直观和自然。调用者无需使用额外的语法来传递指针或对象副本,只需将变量作为参数传递即可。

  4. 实现函数重载:函数重载是 C++ 的一项强大特性。通过使用引用参数,可以在函数签名中使用相同的参数类型,但实际上根据引用的类型选择不同的重载函数。

void Swap(int& left, int& right)
{
   int temp = left;
   left = right;
   right = temp;
}

需要注意的是,传递引用参数可能存在一些潜在的风险。例如,如果函数不需要修改传递的数据,应该使用常量引用来避免意外修改。此外,如果引用参数指向的对象在函数执行期间被销毁,会导致悬空引用的问题。因此,在使用引用参数时需要确保引用的有效性和生命周期。

作为函数的返回值

将 C++ 引用作为函数返回值有以下几个好处

  1. 避免拷贝:返回引用可以避免大型对象的拷贝操作,减少函数返回值的开销。

  2. 可链式操作:返回引用使得函数调用可以使用连续的点运算符进行链式操作。这样的语法更加简洁和直观,提高了代码的可读性。

  3. 支持函数重载:通过使用引用返回值,可以在函数签名中使用相同的返回类型,但根据不同的输入参数选择不同的重载函数。这是 C++ 函数重载的一个重要应用场景。

  4. 可以实现赋值操作的连续性:当返回引用时,可以将函数调用的结果直接赋值给变量,从而实现对返回值的连续操作。这在一些表达式操作和流式编程中非常有用。

需要注意的是,返回引用时需要确保引用指向的对象在函数返回后仍然有效。如果引用指向的对象在函数结束后销毁,则返回悬空引用会导致未定义行为,下面看一个例子:

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]

所以对于这种行为,在每一种编译器下都有不同的结果,需要谨慎使用

引用和指针的区别

  1. 语法和操作

    • 引用使用 & 符号进行声明,指向对象后无需使用符号进行间接访问,直接使用对象名即可。例如:int a = 5; int& ref = a; ref = 10;
    • 指针使用 * 符号进行声明,通过解引用操作符 * 访问指针指向的对象。例如:int a = 5; int* ptr = &a; *ptr = 10;
  2. 空值

    • 引用必须在声明时被初始化,并且不能指向空值(nullptr)。
    • 指针可以在声明时不进行初始化,允许指向空值(nullptr)。
  3. 重新赋值

    • 引用一旦被赋值,不能再引用其他对象。引用在初始化后,就一直引用该对象,不能更改引用的目标对象。
    • 指针可以重新赋值,可以在运行时改变指针指向的对象。
  4. 引用的安全性

    • 引用在使用时不需要进行空指针检查,因为引用必须在初始化时指向有效对象。
    • 指针需要进行空指针检查,避免解引用空指针导致的错误。
  5. 数组和函数的使用

    • 引用无法直接指向数组或函数,必须指向某个对象。
    • 指针可以指向数组或函数,并可以进行数组指针或函数指针的运算。

但实际上,对于引用而言

底层逻辑上可以被视为指针的一种语法糖。在编译器内部,引用通常被实现为指针的别名

通过指针来实现引用的语义

当我们声明一个引用时,编译器会将其转换为一个指针,并与所引用的对象关联起来

在访问引用时,编译器会自动解引用指针,可以直接操作引用所指向的对象,就像操作该对象的直接别名一样

我们可以通过下面的一个例子来理解:

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 关键字只是对编译器的提示,不一定会被采纳

编译器通常会根据函数的复杂度、调用频率和编译器策略等因素来决定是否将函数进行内联展开

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值