
个人主页:星轨初途
个人专栏:C语言,数据结构,C++学习(竞赛类)C++专栏(开发类)
文章目录
- 前言
- 一、C++的第一个程序
- 二、命名空间(namespace)
-
- 1、namespace的价值
- 2、namespace的定义
- [3. 命名空间使用](#3. 命名空间使用)
- 三、C++输入(cin)&输出(cout)
- 四、缺省参数
- 五、函数重载
- 六、引用
-
- [1. 引用的概念和定义](#1. 引用的概念和定义)
- [2. 引用的特性](#2. 引用的特性)
- [3. 引用的使用场景](#3. 引用的使用场景)
- 4、const引用
- [5. 指针和引用的关系](#5. 指针和引用的关系)
- 七、内联函数(inline)
- 八、空指针(nullptr)
- 九、结束语
前言
嗨!我们又见面啦!我们在学完数据结构和C语言后,我们就开始学习C++啦!本专栏主要偏向于C++开发方向,准备好进入C++的世界了吗?让我们一起探索吧!
关于C++的历史,发展及应用这里就不做过多赘述,大家可以在网上搜索进行了解
我们直接从C++的第一个程序进行讲解啦!
一、C++的第一个程序
C++兼容C语言绝大多数的语法,所以C语言实现的 hello world 依旧可以运行。在C++中,我们需要把定义文件代码后缀改为 .cpp,VS编译器看到 .cpp 就会调用C++编译器编译(Linux下要用 g++ 编译,不再是 gcc)。
C版本:
cpp
#include<stdio.h>
int main()
{
printf("hello world");
return 0;
}
虽然兼容C,但C++有一套自己的输入输出。严格来说,C++版本的 hello world 应该是这样写的:
C++版本:
cpp
#include<iostream>
using namespace std; // 这里的std cout等我们下面会依次讲解
int main()
{
cout << "hello world\n" << endl;
return 0;
}
二、命名空间(namespace)
在C++中,变量、函数和后面要学到的类都是大量存在的。这些名称如果都存在于全局作用域中,很容易导致冲突。
1、namespace的价值
namespace 关键字的出现就是针对这种问题的。它的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染 。
比如,在C语言中,如果你包含 <stdlib.h> 后定义一个名为 rand 的全局变量,就会和库里的 rand 函数冲突。C++ 引入 namespace 就是为了更好地解决此类问题。
c
#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main()
{
printf("%d", rand);
return 0;
}
效果

2、namespace的定义
定义命名空间需要使用 namespace 关键字,后面跟命名空间的名字,然后接一对 {}。
namespace本质是定义出一个域,这个域跟全局域各自独立。不同的域可以定义同名变量,所以下面的rand不在冲突了。namespace只能定义在全局,当然还可以嵌套定义。- 项目工程中,多文件中定义的同名
namespace会认为是一个namespace,自动合并,不会冲突。 - C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
- C++标准库都放在一个叫std(standard)的命名空间中。
cpp
namespace xingguichutu
{
int rand = 10; // 命名空间中可以定义变量
int Add(int left, int right)
{ // 也可以定义函数
return left + right;
}
struct Node
{ // 也可以定义类型
struct Node* next;
int val;
};
// 嵌套定义
namespace pg
{
int rand = 1;
}
}
3. 命名空间使用
编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。要使用命名空间中定义的成员,有三种方式:
- 指定命名空间访问(项目中推荐这种方式):
cpp
printf("%d\n", bit::rand);
using将命名空间中某个成员展开(项目中经常访问的不存在冲突的成员推荐这种方式):
cpp
using bit::Add;
- 展开命名空间中全部成员(项目不推荐,冲突风险很大。日常小练习程序为了方便推荐使用):
cpp
using namespace bit;
C++标准库都放在一个叫
std(standard) 的命名空间中。这就是为什么第一个程序里有一句using namespace std;。
三、C++输入(cin)&输出(cout)
<iostream>是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。std::cin是istream类的对象,主要面向窄字符的标准输入流。std::cout是ostream类的对象,主要面向窄字符的标准输出流。std::endl是一个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区。<<是流插入运算符,>>是流提取运算符。(C语言中用作位运算左移/右移)- IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++ IO流的用法,后面我们会有专门的一个章节来细节IO流库。
- cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
C++ IO 的优势:
使用C++输入输出更方便,不需要像 printf/scanf 输入输出时那样需要手动指定格式。C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的)。更重要的是,C++的流能更好地支持自定义类型对象的输入输出。
cpp
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
// 自动识别类型
cin >> a >> b >> c;
cout << a << " " << b << " " << c << endl;
return 0;
}

注意:
cout/cin/endl等都属于C++标准库,放在std命名空间中。日常练习可以使用using namespace std;,但在实际项目开发中,建议使用std::cout或using std::cout;以防止命名冲突。
四、缺省参数
1、缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值(默认值)。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
如:
cpp
void Func(int a = 0)
{
cout << a << endl;
}
// Func(); 会打印 0
// Func(10); 会打印 10
2、全缺省和半缺省
- 全缺省:全部形参都给缺省值。
- 半缺省 :部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
注意:
- 带缺省参数的函数调用,必须从左到右依次给实参,不能跳跃给。
- 函数声明和定义分离时,缺省参数不能在声明和定义中同时出现,规定必须在函数声明中给缺省值。
cpp
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
五、函数重载
C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同(可以是参数个数不同,或者类型不同,或者类型的顺序不同)。这使得C++函数调用表现出了多态行为,使用更灵活。
cpp
// 1. 参数类型不同
int Add(int left, int right);
double Add(double left, double right);
// 2. 参数个数不同
void f();
void f(int a);
// 3. 参数类型顺序不同
void f(int a, char b);
void f(char b, int a);
注意 :返回值不同不能作为重载条件,因为调用时编译器无法区分。
六、引用
1. 引用的概念和定义
引用不是新定义一个变量,而是给已存在变量取了一个别名 。编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
语法:类型& 引用别名 = 引用对象;
cpp
int a = 0;
int& b = a; // b是a的别名
int& c = a; // c也是a的别名
int& d = b; // 也可以给别名取别名
这里 a, b, c, d 的地址完全一样,修改其中任何一个,其他的都会跟着变。
如:
cpp
#include <iostream>
using namespace std;
int main()
{
int a = 0;
int& b = a; // b是a的别名
int& c = a; // c也是a的别名
int& d = b; // 也可以给别名取别名
cout << a << " " << b << " " << c << " " << d << '\n';
cout << &a << " " << &b << " " << &c << " " << &d << '\n';
a++;
cout << a << " " << b << " " << c << " " << d << '\n';
cout << &a << " " << &b << " " << &c << " " << &d << '\n';
return 0;
}
效果

2. 引用的特性
- 引用在定义时必须初始化。
- 一个变量可以有多个引用。
- 引用一旦引用一个实体,再不能引用其他实体
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
// 编译报错: "ra": 必须初始化引用
//int& ra;
int& b = a;
int c = 20;
// 这里并非让b引用c,因为C++引用不能改变指向,
// 这里是一个赋值
b = c;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
3. 引用的使用场景
- 引用传参:类似指针传参,可以在函数内部修改外部变量,且不需要像指针那样解引用,代码更简洁安全。
cpp
#include <iostream>
using namespace std;
// rx 和 ry 分别是外部传入实参的别名,修改它们就是修改外部实参
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
// 调用时无需像C语言那样传地址 Swap(&x, &y),直接传变量即可
Swap(x, y);
cout << x << " " << y << endl; // 此时外部的 x 和 y 已经被成功交换 [cite: 2213]
return 0;
}

- 引用做返回值:减少拷贝,提高效率;同时可以改变引用对象时同时改变被引用对象。(此场景需要注意,不能返回局部变量的引用,因为局部变量出作用域就销毁了)。
cpp
#include <iostream>
using namespace std;
int arr[5] = { 10, 20, 30, 40, 50 }; // 全局数组,生命周期在整个程序运行期间
// 返回类型为 int&,意味着返回的是 arr[index] 这块内存空间的别名,不会发生值拷贝
int& cm(int index)
{
return arr[index];
}
int main()
{
cout << arr[1] << endl; // 打印 20
// 核心高级用法:直接给函数的返回值赋值,从而改变了被引用的实际对象!
cm(1) = 999;
cout << arr[1] << endl; // 打印 999
/* 🔥 致命避坑:千万不要这么写!
int& cm()
{
int temp = 10;
return temp; // 报错或警告:temp是局部变量,出了函数大括号内存就被回收,返回的将是"野引用"
}
*/
return 0;
}
效果

4、const引用
-
可以引用一个
const对象,但是必须用const引用 。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。 -
所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象,C++中把这个未命名对象叫做临时对象。
cpp
const int a = 10;
// int& ra = a; // 报错,权限放大
const int& ra = a; // 正确
注意临时对象:
当发生类型转换(如
double转int)或表达式求值(如a * 3)时,编译器会产生一个具有常性的"临时对象"来存储中间值。此时如果用引用去接,必须使用const引用。
5. 指针和引用的关系
C++中指针和引用就像两个性格迥异的亲兄弟,相辅相成,互相不可替代:
- 内存:引用是取别名,不开空间;指针存储地址,需要开辟空间。
- 初始化与指向:引用定义必须初始化,且不能改变指向;指针可以不初始化,且可以随时改变指向。
- 访问 :引用直接访问;指针需要解引用(
*)访问。 - 安全:指针容易出现空指针和野指针;引用极少出现,相对更安全。
七、内联函数(inline)
用 inline 修饰的函数叫做内联函数。编译时,C++编译器会在调用的地方展开内联函数,从而避免了函数调用建立栈帧的开销,提高效率。
inline的设计目的是为了替代C语言容易出错的宏函数。inline对于编译器而言只是一个建议 。它适用于频繁调用的短小函数。对于递归函数或代码较长的函数,即使加上inline,编译器也会忽略。inline不建议声明和定义分离到两个文件,这会导致链接错误。因为inline被展开后就没有函数地址了。
cpp
inline int aadd(int a,int b)
{
return a + b;
}
八、空指针(nullptr)
在C语言中,NULL 实际上是一个宏,可能被定义为 0 或 (void*)0。在C++中,如果 NULL 被定义为 0,在面临重载函数 f(int) 和 f(int*) 时,调用 f(NULL) 会匹配到 f(int),这违背了我们传入空指针的初衷。
为了解决这个问题,C++11 引入了 nullptr。
nullptr 是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型,但不能被转换为整数类型 。因为nullptr只能被 隐式地转换为指针类型,而不能被转换为整数类型。在C++中,定义空指针请务必使用 nullptr。
cpp
#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(0);
// 本想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。
f(NULL);
f((int*)NULL);
// 编译报错: error C2665: "f": 2 个重载中没有一个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
return 0;
}
九、结束语
嗨ヾ(o´∀`o)ノ!本篇到这里就结束啦!我们从第一个C++程序出发,详细讲解了C++区别于C语言的基础特性:更安全的命名空间、更聪明的输入输出、灵活的缺省参数与重载、好用的引用以及更严谨的内联与空指针。通过本篇我们也正式进入C++的大门啦!٩(๑❛ᴗ❛๑)۶
欢迎大家积极在评论区进行讨论和建议,感谢大家的支持啦!下一篇我们将要讲解类和对象,欢迎大家来学习了解!

