C++入门篇

1. 前言


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

2. 命名空间


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

C++ 复制代码
#include <stdio.h>

int x = 0;

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

	return 0;
}

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

在C语言中,这个问题没有特别好的解决方案;当然,我们可以将全局变量放在一个函数中,返回该变量的值,但在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是命名空间的关键字

C++ 复制代码
#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 命名空间用处

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

C++ 复制代码
#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;
}

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

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

C++ 复制代码
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++中的输入输出


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那样,在输入输出时指定数据类型

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++中,可以不写明实参,而在形参中指定一个缺省值;当然,如果指定了实参,形参是实参的值

  • 在调用有缺省参数的函数时,可以不指定实参,但如果要指定实参,必须从左往右给,不能间断
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. 全缺省参数:

    每个形参都有缺省参数

    C++ 复制代码
    int  Add(int a = 10, int b = 20, int c = 30);
  2. 半缺省参数

    部分参数有缺省参数

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

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

C++ 复制代码
//error
int Add(int a, int b = 10);

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

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

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

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

C++ 复制代码
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++中,可以将指定的大小作为缺省参数,一次性开辟
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在使用指针的时候,发现指针不方便使用和代码的阅读,于是在指针的基础上,加入了引用的概念

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

引用的特性:

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

6.2 引用的用法

  1. 做参数

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

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

    C++ 复制代码
    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接受到的值其实是寄存器中的值;再来看看下面的代码

    C++ 复制代码
    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

C++ 复制代码
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必须加&

C++ 复制代码
int main()
{
	int a = 10;
	auto p1 = &a;
	auto* p2 = &a;
	auto& c = a;

	return 0;
}

9.范围for循环


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

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++中,我们可以更加写的更加简化

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++中定义的关键字,使用时不需要包含头文件

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++中定义的关键字,使用时不需要包含头文件

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;
}
相关推荐
ZZZ_O^O27 分钟前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
小飞猪Jay3 小时前
C++面试速通宝典——13
jvm·c++·面试
rjszcb4 小时前
一文说完c++全部基础知识,IO流(二)
c++
小字节,大梦想4 小时前
【C++】二叉搜索树
数据结构·c++
吾名招财4 小时前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
我是哈哈hh5 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
憧憬成为原神糕手5 小时前
c++_ 多态
开发语言·c++
郭二哈5 小时前
C++——模板进阶、继承
java·服务器·c++
挥剑决浮云 -5 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记
丶Darling.5 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树