【C++】基础入门(详解)
🌟 Hello,我是egoist2023!
🌍 种一棵树最好是十年前,其次是现在!
目录
输入&输出
缺省参数(默认参数)
函数重载
引用
概念及定义
特性及使用
const引用
与指针的关系
内联inline和nullptr
inline
nullptr
输入&输出
前文了解到本贾尼认为C语言是有缺陷的,想弥补其缺陷,最后搞出自己的一套C++体系。其实,C语言的scanf和printf函数是有缺陷且过于冗杂(每次都需要手动指定格式),是否能通过一种方式使输入输出更简便呢?针对此问题在C++中引入了输入&输出流。
- <iostream> (全称: Input Output Stream )是标准的输入、输出流库,定义了标准的输入、输出对象。
- std::cin 是 istream 类的对象,它主要面向窄字符的标准输入流。(C++标准库都封在std的命名空间中)
- std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流。
- std::endl 是⼀个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区(简单看成C语言中的\n)。
- <<是流插入运算符,>>是流提取运算符。(在C语言还充当左移/右移位运算符)。
- 输入输出可以自动识别变量类型(本质是通过函数重载实现的),最重要的是 C++的流能更好的支持自定义类型对象的输入输出。
#include<iostream>
#include<stdio.h>
int main()
{
int a = 1;
std::cout << a;
printf("%d", a);
return 0;
}
- IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,因此在此章节只介绍IO流的用法。
- 在vs编译器中<iostream>间接包含了<stdio.h>,因此可以使用printf。
#include<iostream>
int main()
{
int a = 1;
std::cout << a;
printf("%d", a);
return 0;
}
缺省参数(默认参数)
- 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值。
- 缺省参数分为全缺省(全部形参给缺省值)和半缺省参数(部分形参给缺省值)。
- C++规定半缺省参数必须从左往右依次连续缺省,不能间隔给缺省值。(同理依次给实参)
- 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。(这样做的目的是避免冲突,因为编译器在链接时会合成制表符,此时,声明和定义都有缺省值就会产生冲突)
-
缺省参数的意义远不止如此,在之前的栈和队列章节中,栈的初始化中capacity默认开始为0。若一开始明确插入1000个数据时,初始化时直接开好,避免多次扩容损失效率。(没有传参时会采用缺省参数的值。)
函数重载
在C语言中若要实现Add函数(需要支持整形、浮点型相加)时,需要保证Add函数名字不冲突,为了解决这一困扰。在C++中,添加了函数重载这个概念,其要求这些同名函数的形参不同,可以是参数个数不同或者类型不同(不支持返回类型不同),呈现多态行为,使用更加方便。函数支持三种类型的重载。
函数重载是C++中实现多态性的一种重要方式,具有重要意义(增强了代码的可读性,函数的通用性和复用性),同时为之后的模板(函数重载的支持)、运算符重载做了铺垫。
参数类型不同
int Add(int x, int y)
{
cout << "int Add(int x, int y)" << endl;
return x + y;
}
double Add(double x, double y)
{
cout << "double Add(double x, double y)" << endl;
return x + y;
}
参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
参数顺序不同
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;
}
引用
概念及定义
引用是给已存在变量取一个别名,语法层面上编译器不会为其开辟内存空间, 它和引用的变量共用同一块内存空间。(实际底层实现上开辟了空间,是用一个指针指向其引用的变量)


特性及使用
• 引用在定义时 必须初始化
• 支持多次引用• 引用一个实体,其别名再不能引用其他实体
- 实践中,引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
- 使用C++引用替代指针传参,目的是简化程序,避开复杂的指针。
- 引用传参和引用作返回值在实践中经常用到,达到了减少拷贝提高效率(指针也可以减少拷贝)和改变引用对象的目的。(针对自定义类型更为明显)
在指针章节中要求交换两个变量,涉及到传址调用(形参改变实参)。同样,使用引用也可以达到此目的。
const引用
变量也有被const修饰的情况,此时对其进行引用会报错(权限被放大了),因此需要引入 const引用。
权限要点 -- > 权限可以平移或者缩小,但一定不能放大。
- 引用一个const对象,必须用const引用。const引用也可以引用普通对象,因为对象的访问权限可以缩小,不能放大。
- 需要注意的是 int& rb = a*3; double d = 12.34; int& rd = d 这种场景下需要使用const引用
这里涉及到类型转换,将double转换为int&为何需要使用const引用呢?
在类型转换中,会产生临时对象(也称未命名对象),这个对象具有常性,此时rb和rd都是引用这个临时变量,权限被放大了,因此会出现报错。
- 临时对象是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象。
与指针的关系
由上文可以发现引用与指针的部分功能是类似的,那为什么本贾尼还引入了引用这个语法呢?
引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。如:C++引⽤定义后不能改变指向,相反地指针可以改变指向(链表章节中指针定义的结构是不能用引用替代的)。
引用 | 指针 |
语法上不开空间 | 要开空间存储变量地址 |
必须初始化 | 非必须初始化 |
不能改变指向 | 可以改变指向 |
可以直接访问指向对象 | 需要借助*访问指向对象 |
引用结果为引用类型大小 | 看32位/64位平台 |
\ | 野指针和空指针问题 |
内联inline和nullptr
inline
inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。
#define Add(x,y) ((x)+(y))
inline int Add(const int& x, const int& y)
{
return x + y;
}
在实现Add函数中使用define宏定义时需要考虑各种因素,而使用inline则无需顾虑很多,让编译器决定是否展开此函数。
- inline对于编译器是一个建议(选择性展开),适用于频繁调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。
-
inline不建议声明和定义分离到两个文件 ,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错(不能合成制表符)。
-
vs编译器 debug版本下面默认是不展开inline的,因此需要设置下。
nullptr
引入nullptr实际是为了替代C语言的NULL(有缺陷)。
NULL实际是一个宏,在头文件(stddef.h)中。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
C++中NULL可能被定义为 常量0 ,或者C中被定义为 无类型指针(void*) 的常量。但都不可避免的会遇到一些麻烦。nullptr(关键字)是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用 nullptr定义空指针可以避免类型转换 的问题。(nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型)
若NULL为无类型指针(void*)呢?运行后会报错,这是因为2个函数重载中没有一个能转换所有参数类型。
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(NULL);//调用f(int x)
f(nullptr);//调用f(int* ptr)
f((void*)0);//err
return 0;
}