C++ 入门基础:从 C 到 C++ 的第一步

学过 C 语言再来看 C++,会发现很多东西"似曾相识但又不一样"。这篇博客把入门必须掌握的核心语法都整理清楚,重点标出和 C 的区别,方便复习和练习。


目录

[1. 命名空间 namespace](#1. 命名空间 namespace)

为什么需要它?

定义方式

嵌套命名空间

三种使用方式

[2. C++ 输入输出](#2. C++ 输入输出)

[3. 缺省参数(默认参数)](#3. 缺省参数(默认参数))

[全缺省 vs 半缺省](#全缺省 vs 半缺省)

声明和定义分离时

[4. 函数重载](#4. 函数重载)

[5. 引用](#5. 引用)

概念

引用的三条特性

引用的主要用途

[const 引用](#const 引用)

[引用 vs 指针](#引用 vs 指针)

[6. inline 内联函数](#6. inline 内联函数)

是什么

[inline vs 宏函数](#inline vs 宏函数)

[7. nullptr](#7. nullptr)

问题背景

[C++11 引入 nullptr](#C++11 引入 nullptr)

总结

练习建议


1. 命名空间 namespace

为什么需要它?

C 语言里所有全局变量、函数名都堆在一起,项目大了极容易冲突。比如你自己定义了一个 rand 变量,结果和标准库的 rand() 函数撞名,直接报错:

复制代码
#include <stdlib.h>
int rand = 10;  // 编译报错:rand 重定义,stdlib 里有个 rand() 函数

C++ 用 namespace 解决这个问题------把名字关进一个独立的域里,和全局域互不干扰

定义方式

cpp 复制代码
namespace TomGo  // TomGo 是命名空间名,一般用项目名
{
    int rand = 10;           // 变量

    int Add(int a, int b)    // 函数
    {
        return a + b;
    }

    struct Node              // 类型
    {
        int val;
        Node* next;
    };
}

几个要记住的规则:

  • namespace 只能定义在全局(不能在函数内部)
  • 可以嵌套定义
  • 多个文件里同名的 namespace 会自动合并,不冲突
  • C++ 标准库全部放在 std 这个命名空间里

嵌套命名空间

cpp 复制代码
namespace TomGo
{
    namespace zjy   // 嵌套
    {
        int val = 1;
    }
    namespace zxy
    {
        int val = 2;
    }
}

// 访问时逐层展开
TomGo::zjy::val;  // 1
TomGo::zxy::val;  // 2

三种使用方式

编译器默认只在局部域和全局域查找,不会自动进命名空间找。所以要用命名空间里的东西,有三种方式:

html 复制代码
namespace N
{
    int a = 0;
    int b = 1;
}

// 方式一:指定命名空间访问(项目开发推荐)
printf("%d\n", N::a);

// 方式二:using 展开单个成员(常用成员且不冲突时推荐)
using N::b;
printf("%d\n", b);  // 直接用 b

// 方式三:展开整个命名空间(小练习方便,项目不推荐,风险高)
using namespace N;
printf("%d\n", a);
printf("%d\n", b);

实践建议using namespace std; 在练习中随便用,正式项目里要慎用,因为 std 里名字太多了,容易和自己的代码冲突。


2. C++ 输入输出

C++ 有自己的一套 IO,用 <iostream> 头文件。

cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    int a = 0;
    double b = 0.1;
    char c = 'x';

    // 输出:<< 是流插入运算符
    cout << a << " " << b << " " << c << endl;

    // 输入:>> 是流提取运算符
    cin >> a;
    cin >> b >> c;  // 可以连着写

    return 0;
}

和 printf/scanf 的对比:

C 的 printf/scanf C++ 的 cout/cin
格式串 必须手动写 %d %lf 自动识别类型,不用写
扩展性 只支持内置类型 支持自定义类型(通过运算符重载)
效率 较快 默认有同步开销,竞赛可关闭

关键点:

  • cout 是输出流对象,cin 是输入流对象,都在 std 命名空间里
  • endl = 输出换行 + 刷新缓冲区(等价于 '\n' + flush,如果不需要刷新用 '\n' 更快)
  • <<>> 在 C 里是位运算符,C++ 里复用了这两个符号做 IO

竞赛提速(IO 量大时加这三行):

复制代码
ios_base::sync_with_stdio(false);  // 关闭 C/C++ IO 同步
cin.tie(nullptr);                   // 解除 cin 和 cout 的绑定
cout.tie(nullptr);

加了这三行后,不要再混用 printf/scanf,否则输出顺序可能乱。


3. 缺省参数(默认参数)

函数定义时给参数一个默认值,调用时可以不传。

cpp 复制代码
void Func(int a = 0)
{
    cout << a << endl;
}

Func();    // 不传,a = 0
Func(10);  // 传了,a = 10

全缺省 vs 半缺省

cpp 复制代码
// 全缺省:所有参数都有默认值
void Func1(int a = 10, int b = 20, int c = 30)
{
    cout << a << " " << b << " " << c << endl;
}

Func1();          // 10 20 30
Func1(1);         // 1  20 30
Func1(1, 2);      // 1  2  30
Func1(1, 2, 3);   // 1  2  3

// 半缺省:只有部分参数有默认值,必须从右往左连续给
void Func2(int a, int b = 10, int c = 20)  // a 没有默认值
{
    cout << a << " " << b << " " << c << endl;
}

// 错误写法(中间不能跳):void Func(int a = 1, int b, int c = 3)

调用规则:必须从左往右依次传实参,不能跳着传。

声明和定义分离时

cpp 复制代码
// Stack.h(头文件里写缺省值)
void STInit(ST* ps, int n = 4);

// Stack.cpp(定义里不写缺省值)
void STInit(ST* ps, int n)
{
    // ...
}

规则 :缺省值只能写一次,约定写在声明处。声明和定义同时写会报错。

缺省参数的实际价值:比如初始化栈,不知道容量就用默认 4,知道容量就提前分配好,避免扩容开销。


4. 函数重载

C++ 允许在同一作用域里有多个同名函数,只要参数不同(类型、个数、顺序)就行。C 语言不支持这个。

复制代码
// 参数类型不同
int  Add(int a,    int b)    { return a + b; }
double Add(double a, double b) { return a + b; }

// 参数个数不同
void f() {}
void f(int a) {}

// 参数顺序不同
void f(int a, char b) {}
void f(char b, int a) {}

int main()
{
    Add(1, 2);       // 调用 int 版本
    Add(1.1, 2.2);   // 调用 double 版本
    f();             // 无参版本
    f(10, 'a');      // int, char 版本
    f('a', 10);      // char, int 版本
}

不能作为重载条件的情况:

bash 复制代码
// 仅返回值不同------不构成重载,调用时无法区分
int  fxx() { return 0; }
void fxx() {}           // 报错

// 这两个虽然构成重载,但调用 f1() 时有歧义(编译器不知道调哪个)
void f1() {}
void f1(int a = 10) {}  // f1() 两个都能匹配,调用时报歧义错误

重载的底层原理 :C++ 编译器会对函数名做"名字修饰"(Name Mangling),把参数类型信息编码进符号名里,所以 Add(int, int)Add(double, double) 在底层是不同的符号。C 语言不做这个,所以不支持重载。


5. 引用

概念

引用是给已有变量取一个别名,不开新内存,和原变量共用同一块空间。

cpp 复制代码
int a = 0;
int& b = a;   // b 是 a 的别名
int& c = a;   // c 也是 a 的别名
int& d = b;   // 给别名再取别名,d 还是 a 的别名

++d;          // 实际上是 ++a

// 四个地址完全相同
cout << &a << &b << &c << &d << endl;

语法:类型& 引用名 = 被引用的变量;

注意:& 在这里是引用符,不是取地址符。区分方法:出现在类型旁边是引用,出现在变量旁边是取地址。

引用的三条特性

复制代码
// 1. 定义时必须初始化
int& ra;      // 报错!必须初始化

// 2. 一个变量可以有多个引用(见上面)

// 3. 引用一旦绑定,不能改变指向
int a = 10, c = 20;
int& b = a;
b = c;        // 这不是让 b 指向 c,而是把 c 的值赋给 a
              // b 和 a 的地址仍然相同

引用的主要用途

① 引用传参------替代指针传参,更简洁

复制代码
// C 的做法(指针)
void Swap(int* px, int* py)
{
    int tmp = *px;
    *px = *py;
    *py = tmp;
}

// C++ 的做法(引用)
void Swap(int& rx, int& ry)
{
    int tmp = rx;
    rx = ry;
    ry = tmp;
}

int x = 0, y = 1;
Swap(x, y);   // 不需要传地址,更直观

还有一个实用场景:替代二级指针

html 复制代码
// C 里修改链表头节点需要二级指针
void ListPushBack(LTNode** phead, int x) { ... }

// C++ 里用引用替代,简洁得多
void ListPushBack(LTNode*& phead, int x) { ... }  // 指针变量的引用

② 引用返回值------直接操作数据,避免拷贝

html 复制代码
int& STTop(Stack& st)          // 返回栈顶元素的引用
{
    return st._a[st._top - 1]; // 返回元素本身,不是拷贝
}

// 这样可以直接修改栈顶元素
STTop(st) += 10;  // 等价于 st._a[top-1] += 10

⚠️ 危险陷阱:不要返回局部变量的引用!局部变量函数结束就销毁了,引用会变成悬垂引用(相当于野指针)。

const 引用

复制代码
const int a = 10;
// int& ra = a;       // 报错!权限放大,a 是只读的,ra 却能修改
const int& ra = a;   // 正确,权限缩小或不变都可以

int b = 20;
const int& rb = b;   // 正确,权限缩小(b 可读写,rb 只读)

必须用 const 引用的特殊情况------临时对象:

复制代码
int a = 10;

// int& rb = a * 3;   // 报错!a*3 的结果存在临时对象里,临时对象有"常性"
const int& rb = a * 3;  // 正确

double d = 12.34;
// int& rd = d;       // 报错!类型转换会产生临时对象
const int& rd = d;   // 正确

理解"常性" :编译器生成的临时对象(表达式结果、类型转换中间值)默认是 const 的,所以引用它必须用 const&,否则相当于权限放大。

引用 vs 指针

对比点 引用 指针
内存 不开新空间(语法层面) 开空间存地址
初始化 必须 建议但非强制
能否改变指向 不能
访问 直接访问 需要解引用 *
sizeof 引用对象的大小 始终是 4 或 8 字节
安全性 较安全,少有空引用 容易出现空指针、野指针

两者不是替代关系,各有适合的场景,实践中配合使用。


6. inline 内联函数

是什么

inline 修饰的函数,编译器会在调用处直接展开函数体,不走函数调用(不建立栈帧),从而提高效率。

cpp 复制代码
inline int Add(int x, int y)
{
    return x + y;
}

int main()
{
    int ret = Add(1, 2);   // 编译后相当于 int ret = 1 + 2;(直接展开)
}

inline vs 宏函数

C 语言用宏来做类似的事,但宏很容易出错:

html 复制代码
// 错误的宏实现(常见问题)
#define ADD(a, b) a + b       // 错:cout << ADD(1,2)*5 结果是 1+2*5=11,不是 15
#define ADD(a, b) (a + b)     // 还是错:ADD(x&y, x|y) -> (x&y+x|y) 优先级问题

// 正确写法(每个参数都加括号,整体也加括号)
#define ADD(a, b) ((a) + (b))
对比 宏函数 inline 函数
类型检查
调试 困难 正常调试
写法 容易出错(括号) 正常写函数
展开时机 预处理期(文本替换) 编译期

inline 的限制:

  • 只是对编译器的建议,编译器可以忽略(递归函数、代码量大的函数一般不会被展开)

  • 适合短小、频繁调用的函数

  • 不能声明和定义分离到两个文件,因为展开后没有函数地址,链接会找不到符号

    // F.h
    inline void f(int i); // 声明

    // F.cpp
    void f(int i) { ... } // 定义在另一个文件

    // main.cpp
    f(10); // 链接报错!找不到 f 的定义

解决方式:inline 函数直接把定义写在头文件里。


7. nullptr

问题背景

C 里 NULL 是宏,在 C++ 里被定义为整数 0

复制代码
#ifdef __cplusplus
    #define NULL 0       // C++ 里是整数 0
#else
    #define NULL ((void*)0)  // C 里是 void 指针
#endif

这导致一个经典问题:

复制代码
void f(int x)   { cout << "f(int)" << endl; }
void f(int* p)  { cout << "f(int*)" << endl; }

f(NULL);        // 本想调用指针版本,实际调用了 int 版本!因为 NULL == 0
f((int*)NULL);  // 强转才能调用指针版本

C++11 引入 nullptr

nullptr 是专门表示空指针的关键字,类型是 nullptr_t只能隐式转换为指针类型,不能转为整数

复制代码
f(nullptr);     // 正确调用 f(int*),没有歧义

规则 :C++ 代码里,初始化空指针一律用 nullptr,不用 NULL


总结

特性 C C++
命名空间 ❌ 没有 ✅ namespace
输入输出 printf / scanf cout / cin(自动识别类型)
默认参数 ❌ 没有 ✅ 缺省参数
函数重载 ❌ 不支持 ✅ 支持(名字修饰机制)
引用 ❌ 没有(只有指针) ✅ 引用(别名,不开空间)
内联函数 宏函数(危险) inline(安全)
空指针 NULL(本质是 0) nullptr(专用关键字)

练习建议

  1. 命名空间 :自己写一个 namespace,嵌套两层,练习三种访问方式
  2. 缺省参数:改写一个 C 的栈初始化函数,加上缺省容量
  3. 函数重载 :写 Add 函数支持 int、double、long long 三种版本
  4. 引用 :用引用实现 Swap,再尝试返回引用修改数组元素
  5. const 引用 :试试给常量、临时值(如 a*3)加引用,感受报错信息
  6. nullptr :写两个重载函数 f(int)f(int*),分别用 NULLnullptr 调用,对比结果
相关推荐
Greedy Alg2 小时前
定长内存池学习记录
c++·后端
西魏陶渊明2 小时前
解决异步挑战:Reactor Context 实现响应式上下文传递
开发语言·python
小则又沐风a2 小时前
C++内存管理 C++模板
开发语言·c++
不会写DN2 小时前
如何给 Go 语言的 TCP 聊天服务加上 ACK 可靠送达机制
开发语言·tcp/ip·golang
小李云雾2 小时前
FastAPI 后端开发:文件上传 + 表单提交
开发语言·python·lua·postman·fastapi
llm大模型算法工程师weng2 小时前
Python敏感词检测方案详解
开发语言·python·c#
fengci.2 小时前
php反序列化(复习)(第二章)
android·开发语言·学习·php
ZHENGZJM2 小时前
后端基石:Go 项目初始化与数据库模型设计
开发语言·数据库·golang
拾贰_C2 小时前
【Claude Code | bash | install】安装Claude Code
开发语言·bash