C++入门篇

本文介绍了C++中命名空间的使用、如何解决C语言中的变量冲突、函数重载的原理、引用的概念及其应用、内联函数的优化以及C++的空指针处理。通过对比C语言,突出了C++在这些问题上的改进。
摘要由CSDN通过智能技术生成

1. 前言


C++是对C语言的补充和完善;在C语言中,有些问题没有得到很好的解决,在C++中,针对这些问题有了解决方法;因此,在学习C++的语法过程中,会对比着C语言,比较两者的区别

2. 命名空间


在C语言中,我们知道局部变量和全局变量的定义是可以重名的,这时想要打印重名的变量,编译器会以局部优先原则,打印局部变量

#include <stdio.h>

int x = 0;

int main()
{
	int x = 1;
	printf("%d\n", x);// 输出1

	return 0;
}

那如果我就是想要打印全局变量的那个值,该怎么办呢?

在C语言中,这个问题没有特别好的解决方案;当然,我们可以将全局变量放在一个函数中,返回该变量的值,但在C++中,引入了命名空间的概念

#include <iostream>

int x = 0;

int main()
{
	int x = 1;
	printf("%d\n", x);// 1
	printf("%d\n", ::x);// 0
	return 0;
}

在讲命名空间之间,先来说说C++中域的概念:

C++中有基本的4个域:

  1. 全局域
  2. 局部域
  3. 命名空间域
  4. 类域

编译器在查找一个变量时:

  • 如果该变量没有指定域,那么先去局部域查找,找不到再去全局域查找
  • 如果该变量指定了域,那么直接去指定域查找

上面的【::】叫做域作用限定符,如果【::】的前面什么都不加,表示指定了变量x在全局域中,因此编译器就会直接去全局域中找名为x的变量

2.1 命名空间的定义

命名空间的定义与结构体类似,namespae是命名空间的关键字

#include <iostream>

//命名空间的定义
namespace v
{
	int x = 0;
}

int main()
{
	int x = 1;
	printf("%d\n", x);// 1
	printf("%d\n", v::x);// 0
	return 0;
}

2.2 命名空间用处

上面说到,命名空间可以在两个域的变量重名的情况下,输出指定的域的变量,但命名空间更多是为了解决文件中变量重名的问题

#include <iostream>
#include <stdlib.h>

int rand = 10;

int main()
{
	printf("%d\n", rand);
	return 0;
}
//由于头文件<stdlib.h>展开后,有个rand()函数,跟我们定义的rand变量重名了,因此编译器会报错
//这时可以对变量rand使用命名空间

#include <iostream>
#include <stdlib.h>

namespace v
{
	int rand = 10;
}

int main()
{
	printf("%p\n", rand);// 打印的是库函数rand()的地址
	printf("%d\n", v::rand);// 打印命名空间域中的变量rand
	return 0;
}

同样的,在多个文件中,可以会出现重名的变量,结构体,函数名等,这时可以对它们使用命名空间

除了使用命名空间域::变量的方式,我们还可以将命名空间展开

namespace v
{
	int a = 10;

	int Add(int a, int b)
	{
		return a + b;
	}
    
    struct QNode
	{
		int val;
		struct QNode* next;
	};
}
//上面一段代码在其他文件中定义

#include <iostream>
#include "List.h"
using namespace v;// 命名空间展开

int main()
{
	printf("%d\n", a);// 10
	printf("%d\n", Add(10, 20));// 30
    struct v::QNode node1;
	return 0;
}

3. C++中的输入输出


#include <iostream>
using namespace std;

int main()
{
	int n = 0;
	cin >> n;
	cout << "n = " << n << endl;
	return 0;
}

上面的代码是C++中一个简单的输入输出

  • cout,endl,cin是C++库中的函数,使用时必须引用头文件
  • cout是标准输出函数(控制台);cin是标准输入函数(键盘);endl表示换行
  • << 是流插入运算符;<< 是流提取运算符

相较于C,C++的输入输出函数会自动识别数据类型,不需要像C那样,在输入输出时指定数据类型

#include <iostream>
using namespace std;

int main()
{
	char ch = 0;
	int i = 0;
	double d = 0;
	char arr[10] = { 0 };
	cin >> ch >> i >> d >> arr;
	cout << ch << " " << i << endl;
	cout << d << " " << arr << endl;
	return 0;
}
//输入c 10 3.2 abcdef
//输出c 10 3.2 abcdef

4. 缺省参数


4.1 缺省参数的概念

C语言中,调用函数时必须写明实参,否则编译器会报错;C++中,可以不写明实参,而在形参中指定一个缺省值;当然,如果指定了实参,形参是实参的值

  • 在调用有缺省参数的函数时,可以不指定实参,但如果要指定实参,必须从左往右给,不能间断
int  Add(int a = 10, int b = 20)
{
	return a + b;
}

int main()
{
	int ret = Add();
	cout << ret << endl;// 30
	
	ret = Add(1);
	cout << ret << endl;// 21

	ret = Add(1, 2);
	cout << ret << endl;// 3

	return 0;
}

4.2 缺省参数的类型

  1. 全缺省参数:

    每个形参都有缺省参数

    int  Add(int a = 10, int b = 20, int c = 30);
    
  2. 半缺省参数

    部分参数有缺省参数

    • 半缺省参数必须从右往左给,不能间断
    int  Add1(int a, int b, int c = 30);
    
    int  Add2(int a, int b = 20, int c = 30);
    

注意:同一个形参不能在函数的声明和定义都有缺省参数,否则编译器会报错

//error
int Add(int a, int b = 10);

int Add(int a, int b = 10)
{
    return a + b;
}

之后我们写代码,避免不了要在头文件中声明函数,在源文件中定义函数,此时想要使用缺省参数,应该在函数的声明时使用,还是在函数的定义中使用呢?答案是要在函数的声明时使用缺省参数

为什么必须要在函数的声明时使用缺省参数呢?

因为如果是在函数定义使用缺省参数,进行语法检查时,会发现形参和实参不匹配

int Add(int a, int b);

int Add(int a, int b = 10)
{
	return a + b;
}

int main()
{
	int ret = Add(1);
	cout << ret << endl;
	return 0;
}

4.3 缺省参数的使用

有了缺省参数,我们更加灵活的使用函数调用

  • 我们之前写的顺序表中,起始空间是定好的,当空间不够了进行增容,但如果我已经知道需要多大的空间,比如100个元素的大小,按照原来的代码,还要扩容;在C++中,可以将指定的大小作为缺省参数,一次性开辟
void SeqInit(SeqList* psl, int n = 4);

void SeqInit(SeqList* psl, int n)
{
    //...
}

int main()
{
	//开辟100个元素的空间
	SeqList sl;
	SeqInit(&sl, 100);

	//开辟10个元素的空间
	SeqList s2;
	SeqInit(&sl, 10);

	//不清楚开辟的空间
	SeqList s3;
	SeqInit(&sl);

	return 0;
}

5.函数重载


C语言中不允许定义同名的函数,在C++中,只要参数不同,即使函数名相同,这两个函数也能正常使用

函数重载的底层原理是什么呢?在讲这个问题之前,需要了解一下文件的编译过程

我们都知道,编译一个文件分为预编译,编译,汇编和链接这几个步骤:

  • 预编译:会将头文件展开,宏替换,条件编译,去注释等一系列操作
  • 编译:首先从上往下进行语法检查,如果遇到不认识的函数或变量,会报错并停止编译;但如果函数或变量提前声明过,则编译器会通过语法检查,并将源代码转换成汇编代码
  • 汇编:将各种符号汇总,生成符号表;如果函数有定义,则会根据函数名和函数地址,生成符号表,而如果函数只有声明,符号表只会包含函数名;最后转换成二进制代码
  • 链接:对于没有确定符号表的文件,会根据符号表的函数名去其他文件的符号表一一寻找,如果没有找到,表示链接失败;最后将文件合并

在这里插入图片描述

C语言中,仅仅根据函数名来生成符号表;因此如果两个函数的函数名相同,在链接过程中就无法区分两个函数;而C++生成符号表还根据返回值,参数类型,因此即使两个函数的函数名相同,只要参数不同,它们最终生成的符号表就不会相同

不同的编译器符号表的命名规则不同,这里就拿Linux的举例在这里插入图片描述

6. 引用


6.1 引用的概念

C语言有个非常关键的概念,叫指针;想要在函数中修改函数外的内容,C语言中大部分都是使用指针;甚至有些地方不得不使用二级指针;C++创始人Bjarne Stroustrup在使用指针的时候,发现指针不方便使用和代码的阅读,于是在指针的基础上,加入了引用的概念

  • 引用,简单点讲是取别名;对一个变量引用,相当于给该变量取别名
int main()
{
	int a = 0;
	int& b = a;
	int& c = b;
	return 0;
}
// b和c都是a的别名,三者的地址是一样的,对任意一个变量修改都会影响到其他两个变量

引用的特性:

  • 引用的对象必须初始化
  • 可以对一个对象多次引用
  • 一旦引用一个实体,就不能再引用其他实体

6.2 引用的用法

  1. 做参数

    在C语言中,如果想用函数实现两个数的交换,函数的形参类型必须是指针,同时必须解引用指针;C++中只需将参数改成引用,就可以访问函数外的对象了

    //C
    void Swap(int* p1, int* p2)
    {
    	//...
    }
    
    //C++
    void Swap(int& p1, int& p2)
    {
    	int temp = p1;
    	p1 = p2;
    	p2 = temp;
    }
    
  2. 做返回值

    int Add(int a, int b)
    {
    	int c = a + b;
    	return c;
    }
    
    int main()
    {
    	int ret = Add(1, 2);
    	return 0;
    }
    

    上述代码中,我们可能会有疑惑,Add函数调用结束后栈帧被销毁了,为什么还能得到c的值;实际上,Add函数在返回之前,将c的值存储在寄存器当中,ret接受到的值其实是寄存器中的值;再来看看下面的代码

    int& Add(int a, int b)
    {
    	int c = a + b;
    	return c;
    }
    
    int main()
    {
    	int& ret = Add(1, 2);
    	cout << ret << endl;// 打印3
    	Add(3, 4);
    	cout << ret << endl;// 打印7
    	return 0;
    }
    

    将返回值改成引用,发现并没有修改ret的值自己被修改了

    在这里插入图片描述

    因此,想要用引用做返回值,返回对象的空间必须是栈帧销毁后仍然存在,比如堆,静态区上的空间

6.3 引用和指针的区别

在这里插入图片描述

从底层的角度看,引用其实还是指针

  1. 没有多级引用,有多级指针
  2. 引用和实体公用一块空间,指针自己有空间
  3. 引用在定义时必须初始化,指针可以初始化也可以不初始化
  4. 引用不能改变指向,指针可以

7.内联函数


我们都知道,调用函数需要建立栈帧;但如果函数足够简单,而调用的次数又非常多,建立栈帧效率非常低;C++中被inline修饰的函数叫做内联函数,被修饰后,函数不会开辟栈帧,而是在调用的地方展开,以提高程序的效率

在这里插入图片描述

inline是一种以空间换时间的做法,可能会使代码变长,但在一定程度上提高程序效率

inline只适用于规模较小的函数,对于递归或规模大一些的函数,即使加了inline修饰,编译器也不会展开

8.auto关键字


auto可以自动识别数据类型,当一些变量的类型很长,而我们又需要对它进行修改,可以考虑使用auto

int Add(int a, int b){}
int Del(int a, int b) {}

int main()
{
	int (*p)(int, int) = Add;
	auto p = Del;
	return 0;
}

使用auto声明指针时,auto和auto*没有区别;声明引用时,auto必须加&

int main()
{
	int a = 10;
	auto p1 = &a;
	auto* p2 = &a;
	auto& c = a;

	return 0;
}

9.范围for循环


在C语言中,通常使用下面的方式去遍历数组

int main()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	int sz = sizeof(array) / sizeof(array[0]);
	for (int i = 0; i < sz; i++)
	{
		cout << array[i] << endl;
	}
	return 0;
}

在C++中,我们可以更加写的更加简化

int main()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	for (auto& e : array)
	{
		cout << e << endl;
	}
	return 0;
}

10.C++中的空指针


C++中,指针表示空最好不好使用NULL,因为NULL在C++中被定义成了0,有可能引起传参的错误,而应当使用nullptr,这时C++中定义的关键字,使用时不需要包含头文件

void Fun(int n)
{
	cout << "Fun1(int n)" << endl;
}

void Fun(int* p)
{
	cout << "Fun2(int* p)" << endl;
}

int main()
{
	Fun(0);// 打印Fun1(int n)
	Fun(NULL);// 打印Fun1(int n)
	Fun(nullptr);// 打印Fun2(int* p)

	return 0;
}

0;
}


在C++中,我们可以更加写的更加简化

```C++
int main()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	for (auto& e : array)
	{
		cout << e << endl;
	}
	return 0;
}

10.C++中的空指针


C++中,指针表示空最好不好使用NULL,因为NULL在C++中被定义成了0,有可能引起传参的错误,而应当使用nullptr,这时C++中定义的关键字,使用时不需要包含头文件

void Fun(int n)
{
	cout << "Fun1(int n)" << endl;
}

void Fun(int* p)
{
	cout << "Fun2(int* p)" << endl;
}

int main()
{
	Fun(0);// 打印Fun1(int n)
	Fun(NULL);// 打印Fun1(int n)
	Fun(nullptr);// 打印Fun2(int* p)

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值