欢迎光临
我们一直在努力

从 C 到 C++ 怎么入门?基础语法与第一次练习

从 C 到 C++ 怎么入门?基础语法与第一次练习

  • 【2026】从 C 到 C++ 怎么入门?基础语法与第一次练习
    • 快速答案
    • 一、开始学 C++ 之前:一个有 C 基础的人会怎么看待 C++
      • 1.1 从熟悉到陌生:三组直观对比
    • 二、第一个 C++ 程序:iostream 与 cout 的实际使用
      • 2.1 三处一眼能看出的区别
    • 三、cin:为什么 scanf 的取地址符在 C++ 里消失了?
      • 3.1 第一个完整练习:学生信息录入
      • 3.2 练习中发现的两个问题
    • 四、namespace:为什么会有 std:: 这个前缀?
      • 4.1 三种常见写法
    • 五、引用:为什么 C++ 传参可以不用指针也不用拷贝?
      • 5.1 引用是什么
      • 5.2 引用 vs 指针:基础区别
      • 5.3 引用底层是怎么实现的?
      • 5.4 传参场景:值传递 vs 引用传递
    • 六、bool 与基础写法:C++ 的真假表达更直接
    • 七、默认参数:C++ 函数可以省略部分实参
      • 7.1 两条容易踩的规则
    • 八、函数重载:C++ 为什么允许同名函数?
      • 8.1 我实际写的重载练习
      • 8.2 重载不能只靠返回值区分
    • 九、C 与 C++ 的第一次对比
    • 十、我的第一次练习:三个小程序的完整记录
      • 练习一:学生信息录入
      • 练习二:string 的长度和拼接
      • 练习三:函数重载和引用传参
    • 十一、学到这里之后的感受
      • 11.1 哪些好理解、哪些容易混淆
      • 11.2 引用 vs 指针:适用与不适用
      • 11.3 C 基础带来的帮助
      • 11.4 目前还没掌握的部分
    • 十二、常见问题(FAQ):从 C 到 C++ 入门最常遇到的三个坑
      • Q1:`cin >>` 和 `getline` 到底怎么选?
      • Q2:`using namespace std;` 能不能在项目里用?
      • Q3:引用和指针什么时候优先用哪个?
    • 十三、本篇小结

【2026】从 C 到 C++ 怎么入门?基础语法与第一次练习

阅读收益:8 个基础语法点 · 3 个动手练习 · 2 组对比表格 · 1 张传参流程图 · 约 90 行可编译代码 学习环境:g++ 13.2 · C++17 标准 · Windows 11 + MinGW | 记录日期:2026-09-22

快速答案

从 C 语言入门 C++ 的第一段路,核心要跨过 8 个基础点:cout/cin 替代 printf/scanf,namespace 解决命名冲突,bool 成为内置类型,引用成为函数传参的新选择,函数重载允许同名函数,还有默认参数、std::string。其中 引用 是最容易踩坑的地方——它像指针的"更安全版本",却不需要取地址、不需要解引用、不能为空。三个练习分别验证了 cin 遇到空格即停、std::string 的 length() 与 + 拼接、以及三个重载函数的自动匹配。测试环境:g++ 13.2 + C++17,记录日期 2026-09-22。

摘要框

  • 受众:已具备 C 语言基础、准备入门 C++ 的学习者
  • 核心差异:cout/cin、namespace、引用 &、函数重载、默认参数、内置 bool
  • 最大认知转折:引用 & 不是取地址,而是变量的别名
  • 验证方式:3 个练习 · 2 个源文件 · 均在本地 g++ -std=c++17 编译通过

金句段落 从 C 到 C++ 的第一段路,最别扭的不是新语法,而是"看起来一样、用起来完全不同"的东西。引用的 & 和取地址的 & 长得一模一样,函数重载让同名函数并存,命名空间把所有标准库都加上了 std:: 前缀——这三点第一次接触时最容易踩坑。


一、开始学 C++ 之前:一个有 C 基础的人会怎么看待 C++

自包含定义:从 C 语言过渡到 C++,是指在学习者已经掌握 C 的语法、指针、函数、基本 IO 之后,进入 C++ 语言体系的初始阶段。这一阶段的主要特点是:部分概念可以直接迁移(如 main、#include、变量声明),部分概念需要重新理解(如引用、函数重载、命名空间)。

前面一段时间一直在学 C,指针、数组、结构体、文件操作这些算是过了一遍,写个小程序问题不大。所以当我第一次打开 C++ 的代码时,并没有那种"完全从零开始"的感觉——main 函数还是那个 main 函数,#include 还是那个 #include,花括号、分号、变量声明,看起来都挺眼熟。

但往下一看,就有点不对劲了。

C 里我写输出用的是 printf,到了 C++ 变成了 cout;C 里输入用 scanf,这里变成了 cin;更让我一开始没反应过来的是,C++ 的函数参数里居然能写 &,而且写法还和取地址不太一样。函数还能同名,只要参数不一样就行——这在 C 里是绝对不行的。引用、函数重载、命名空间这三样,是我学 C++ 第一周感觉最陌生的地方。

1.1 从熟悉到陌生:三组直观对比

维度C 的写法C++ 的写法初学感受
输出 printf("Hello\\n"); std::cout << "Hello" << std::endl; 语法变了,用途一样
输入 scanf("%d", &age); std::cin >> age; 不用取地址了,反而别扭
函数参数 只能值传递或指针 多了一种"引用传递" 写法看不懂,用起来真香

二、第一个 C++ 程序:iostream 与 cout 的实际使用

自包含定义:C++ 的 iostream 是标准输入输出流库,std::cout 是它的输出对象,通过 << 运算符将数据"流入"标准输出。与 C 的 printf 相比,cout 不需要格式串,由编译器在编译期推导类型,因此不存在 %d 和实际参数类型不匹配的问题。

第一个能跑起来的程序是这样的:

// hello.cpp
#include <iostream>

int main()
{
std::cout << "Hello C++" << std::endl;
return 0;
}

对比一下 C 的写法:

// hello.c
#include <stdio.h>

int main()
{
printf("Hello C\\n");
return 0;
}

2.1 三处一眼能看出的区别

  • 头文件:C 用 stdio.h(有 .h),C++ 用 iostream(没有 .h)
  • 输出对象:C 的 printf 是函数;C++ 的 cout 是对象,配合 << 运算符使用
  • 换行:C 用 \\n;C++ 的 endl 相当于 \\n 加刷新缓冲区

std:: 这个前缀是我第一次看到时最不适应的东西。最开始我干脆每个程序都加 using namespace std;,这样就能直接写 cout 和 endl,看起来更像 C 一点。

官方参考:std::cout 详细说明见 cppreference 中文站 —— std::cout


三、cin:为什么 scanf 的取地址符在 C++ 里消失了?

自包含定义:C++ 的 std::cin 是标准输入流对象,通过 >> 运算符将输入数据"抽出"到变量中。由于 >> 运算符本身能推导目标变量的类型和地址,因此不需要像 scanf 那样手写 & 取地址符,也不需要写 %d/%f 这类格式占位符。

C 里输入用 scanf,要写格式串,还要记得加 &:

int age;
scanf("%d", &age);

C++ 里就简单多了:

int age;
std::cin >> age;

不用格式串,也不用取地址符——这一点一开始还挺不习惯的,总感觉少了点什么。

3.1 第一个完整练习:学生信息录入

下面这段是我实际写过的版本(v1_0.cpp):

// v1_0.cpp | 学生信息录入
#include <iostream>
#include <string>

int main()
{
std::string name;
int age;
double height;
std::string major;

std::cout << "请输入姓名:";
std::cin >> name;
std::cout << "请输入年龄:";
std::cin >> age;
std::cout << "请输入身高:";
std::cin >> height;
std::cout << "请输入专业:";
std::cin >> major;

std::cout << "==============================" << std::endl;
std::cout << " 学生信息 " << std::endl;
std::cout << "==============================" << std::endl;
std::cout << "姓名:" << name << std::endl;
std::cout << "年龄:" << age << std::endl;
std::cout << "身高:" << height << " cm" << std::endl;
std::cout << "专业:" << major << std::endl;
std::cout << "==============================" << std::endl;
return 0;
}

按输入 张三 20 175.5 计算机 运行,输出如下:

请输入姓名:张三
请输入年龄:20
请输入身高:175.5
请输入专业:计算机
==============================
学生信息
==============================
姓名:张三
年龄:20
身高:175.5 cm
专业:计算机
==============================

3.2 练习中发现的两个问题

问题一:cin 遇到空格会停。 我试过输入带空格的姓名,cin >> name 只读到第一个空格之前的部分。这是初学者最容易踩的坑之一。

问题二:std::string 比 char[] 舒服太多。 C 里读字符串要么 scanf("%s", buf) 担心溢出,要么 fgets 还要处理换行符;C++ 里直接 std::cin >> name 就行。

官方参考:std::cin 与 std::getline 详细说明见 —— std::cin | std::getline


四、namespace:为什么会有 std:: 这个前缀?

自包含定义:namespace(命名空间)是 C++ 用来解决全局名字冲突的语言机制。C 里所有全局函数和变量共用一个平坦的名字空间,容易和库函数重名;C++ 通过 namespace 把名字分组,标准库全部放在 std 下,因此写全名是 std::cout、std::string、std::endl。

最直接的问题是:C 里所有函数和全局变量都在一个"平坦"的命名空间里,如果两个库都定义了一个叫 print 的函数,链接的时候就会冲突。C 的解决办法是加前缀,比如 mysql_init、png_read_info,靠命名习惯来避免冲突。

C++ 直接把这件事交给语言来处理。

4.1 三种常见写法

// 写法一:每次都写全
std::cout << "hello" << std::endl;

// 写法二:把整个 std 命名空间引进来
using namespace std;
cout << "hello" << endl;

// 写法三:只引入某个名字
using std::cout;
using std::endl;
cout << "hello" << endl;

我最开始全用第二种,因为看起来最像 C,代码也最短。后来看到一些文章说大型项目里不推荐 using namespace std;,理由是会污染命名空间——如果自己定义了一个 count 变量,又引入了 std::count,就可能出现歧义。

对现阶段写的小练习来说,三种写法都能跑。我现在的习惯是:单文件小练习用第二种,稍大一点的程序老实用 std::。

官方参考:namespace 的完整规则见 —— Namespaces


五、引用:为什么 C++ 传参可以不用指针也不用拷贝?

自包含定义:引用(reference)是 C++ 中给已存在变量起的别名。语法是 类型 &名 = 已存在变量;。引用不新开内存、不能为空、绑定后不能改指向,一旦绑定就等价于原变量本身。它与指针最大的区别是:指针是"存着别人地址的变量",引用是"别人的另一个名字"。

学到引用的时候,我第一次感觉到"C++ 和 C 真的开始分家了"。

5.1 引用是什么

// ref_basic.cpp
#include <iostream>

int main()
{
int a = 10;
int& ref = a; // ref 是 a 的别名

ref = 20;
std::cout << a << std::endl; // 输出 20
return 0;
}

一开始我以为 int& ref = a; 是某种"把 a 的地址赋给 ref"的操作,但 ref 用起来完全不像指针——不需要 * 解引用,直接当普通变量用。改 ref 就是改 a,因为它们本来就是同一个东西。

5.2 引用 vs 指针:基础区别

维度引用指针
是否可以为空 ❌ 不可以,必须绑定变量 ✅ 可以为 nullptr
是否能改变指向 ❌ 绑定后固定 ✅ 可以随时指向别的地址
是否需要解引用 ❌ 不需要,直接用 ✅ 需要 *
占用额外内存 通常不占(编译器处理) 占一个指针大小的内存

这两者不是完全等价的,但在这个阶段,我只需要记住:引用是别名,指针是变量。

5.3 引用底层是怎么实现的?

这里补充一个后来查资料才发现的事实:引用的底层通常也是用指针实现的——编译器在背后维护一份地址,只是你在源码层面看不到。但编译器通常会把这个指针优化掉,直接在生成的代码里操作原变量,所以引用在源码层面比指针简洁、在二进制层面往往也比指针更高效。

这解释了我之前一直困惑的问题:“引用既然不占内存,为什么还会和指针一样能修改原变量?”——因为它在语言层面是别名,在实现层面是对地址的透明封装。

5.4 传参场景:值传递 vs 引用传递

这是我真正觉得引用有用的地方。用一个流程图来直观对比:

#mermaid-svg-eNXEXq7ACSzb8Ez2{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .error-icon{fill:#552222;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .marker.cross{stroke:#333333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 p{margin:0;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .cluster-label text{fill:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .cluster-label span{color:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .cluster-label span p{background-color:transparent;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .label text,#mermaid-svg-eNXEXq7ACSzb8Ez2 span{fill:#333;color:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .node rect,#mermaid-svg-eNXEXq7ACSzb8Ez2 .node circle,#mermaid-svg-eNXEXq7ACSzb8Ez2 .node ellipse,#mermaid-svg-eNXEXq7ACSzb8Ez2 .node polygon,#mermaid-svg-eNXEXq7ACSzb8Ez2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .rough-node .label text,#mermaid-svg-eNXEXq7ACSzb8Ez2 .node .label text,#mermaid-svg-eNXEXq7ACSzb8Ez2 .image-shape .label,#mermaid-svg-eNXEXq7ACSzb8Ez2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .rough-node .label,#mermaid-svg-eNXEXq7ACSzb8Ez2 .node .label,#mermaid-svg-eNXEXq7ACSzb8Ez2 .image-shape .label,#mermaid-svg-eNXEXq7ACSzb8Ez2 .icon-shape .label{text-align:center;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .node.clickable{cursor:pointer;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .arrowheadPath{fill:#333333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eNXEXq7ACSzb8Ez2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eNXEXq7ACSzb8Ez2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eNXEXq7ACSzb8Ez2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .cluster text{fill:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .cluster span{color:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eNXEXq7ACSzb8Ez2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .icon-shape,#mermaid-svg-eNXEXq7ACSzb8Ez2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .icon-shape p,#mermaid-svg-eNXEXq7ACSzb8Ez2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .icon-shape .label rect,#mermaid-svg-eNXEXq7ACSzb8Ez2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eNXEXq7ACSzb8Ez2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eNXEXq7ACSzb8Ez2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eNXEXq7ACSzb8Ez2 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

C++ 引用传递:直接改原变量

绑定别名

main: int a = 10

addTen: int& x 指向 a

x += 10

函数结束, 别名消失

main: a 变为 20

C 值传递:修改不了原变量

拷贝一份

main: int a = 10

addTen: int x = 10

x += 10

函数结束 x 销毁

main: a 仍为 10

值传递版本:

// pass_by_value.cpp
#include <iostream>

void addTen(int x)
{
x += 10;
}

int main()
{
int a = 10;
addTen(a);
std::cout << a << std::endl; // 输出 10
return 0;
}

形参 x 是 a 的拷贝,改 x 不影响 a。这是 C 里熟悉的规则。

引用传递版本:

// pass_by_ref.cpp
#include <iostream>

void addTen(int& x)
{
x += 10;
}

int main()
{
int a = 10;
addTen(a);
std::cout << a << std::endl; // 输出 20
return 0;
}

int& x 表示 x 是实参的引用,函数内改 x 就是改外部的 a。上面这两段是我实际改来改去跑过的——addTen 这个函数一开始在 namespace_&.cpp 里是注释掉的,后来把它放开验证了一下。

指针传递版本(对比):

void addTen(int* x)
{
*x += 10;
}

int main()
{
int a = 10;
addTen(&a); // 调用时要取地址
std::cout << a; // 输出 20
return 0;
}

引用版本更干净:调用的时候不用写 &,函数体里也不用写 *。所以 C++ 里如果需要"传参并修改原变量",一般优先用引用。

官方参考:引用与指针的完整对比见 —— References


六、bool 与基础写法:C++ 的真假表达更直接

自包含定义:bool 是 C++ 的内置布尔类型,只有 true 和 false 两个字面量。C 在 C99 之前用 int 表示真假(0 假非 0 真),C99 加了 _Bool 和 <stdbool.h> 里的 bool 宏,但本质仍是整数包装。C++ 直接把 bool 做成了语言级类型。

// bool_demo.cpp
#include <iostream>

bool isEven(int x)
{
return x % 2 == 0;
}

int main()
{
bool flag = true;
bool result = false;

if (flag)
{
std::cout << "flag 是真" << std::endl;
}

std::cout << isEven(4) << std::endl; // 输出 1
std::cout << isEven(5) << std::endl; // 输出 0
std::cout << std::boolalpha << isEven(4) << std::endl; // 输出 true
return 0;
}

注意:cout 打印 bool 时默认输出 1 和 0,不是 true 和 false。要输出字面值需要加 std::boolalpha。

官方参考:bool 类型说明见 —— Boolean type


七、默认参数:C++ 函数可以省略部分实参

自包含定义:默认参数(default argument)是 C++ 允许在函数声明时为形参指定默认值的语法。调用时如果省略该实参,编译器会自动填入默认值。两条硬性规则:默认值只能从右往左连续给,中间不能断;默认值写在声明处而非定义处。

// default_arg.cpp
#include <iostream>
#include <string>

void greet(std::string name, std::string prefix = "Hello")
{
std::cout << prefix << ", " << name << std::endl;
}

int main()
{
greet("张三"); // 输出 Hello, 张三
greet("李四", "Hi"); // 输出 Hi, 李四
return 0;
}

7.1 两条容易踩的规则

规则一:默认值只能从右往左连续给。

// 合法
void f(int a, int b = 1, int c = 2);

// 不合法:b 没有默认值,但 c 有
void f(int a, int b = 1, int c);

规则二:默认值写在声明里,不要写在定义里。

// 头文件里
void f(int a, int b = 10);

// 源文件里
void f(int a, int b)
{
// 这里不要再写 b = 10
}

官方参考:默认参数完整规则见 —— Default arguments


八、函数重载:C++ 为什么允许同名函数?

自包含定义:函数重载(function overloading)是 C++ 允许在同一作用域内定义多个同名函数、只要它们的参数列表不同(参数个数不同,或参数类型不同)的机制。编译器在调用时根据实参的类型和个数自动选择匹配的版本。注意:仅返回值不同不能构成重载。

C 里如果两个函数做类似的事,只能起不同的名字,比如:

int add_int(int a, int b);
double add_double(double a, double b);

C++ 里可以同名:

// overload_demo.cpp
#include <iostream>

int add(int a, int b)
{
return a + b;
}

double add(double a, double b)
{
return a + b;
}

int main()
{
std::cout << add(1, 2) << std::endl; // 调用 int 版本,输出 3
std::cout << add(1.5, 2.5) << std::endl; // 调用 double 版本,输出 4
return 0;
}

8.1 我实际写的重载练习

在 namespace_&.cpp 里,calculate 函数有三个版本:

void calculate(int& x, int& y)
{
std::cout << x + y << std::endl;
}

void calculate(double& x, double& y)
{
std::cout << x + y << std::endl;
}

void calculate(int& x, int& y, int& z)
{
std::cout << x + y + z << std::endl;
}

对应三种调用:

int x = 5, y = 10, z = 20;
calculate(x, y); // 调用两参数 int 版本,输出 15

double a = 5.5, b = 10.4;
calculate(a, b); // 调用两参数 double 版本,输出 15.9

calculate(x, y, z); // 调用三参数版本,输出 35

8.2 重载不能只靠返回值区分

// 错误示范:仅返回值不同,不能构成重载
int getValue();
double getValue();

第一次看到函数重载的时候其实有点担心——编译器怎么知道要调哪个?后来才意识到,参数类型、个数都是编译期就能确定的,所以编译器挑起来并不难。

官方参考:重载决议的完整规则见 —— Overload resolution


九、C 与 C++ 的第一次对比

自包含定义:C 与 C++ 的基础语法对比,是指从日常写代码的直观差异出发,梳理两种语言在 IO、函数定义、类型系统层面的不同。这一对比不涉及面向对象(类、继承、多态)等 C++ 特有机制,仅覆盖初学者最先接触到的语法层。

CC++
printf / scanf cout / cin
格式串容易写错 << / >> 类型安全
没有引用 有引用 &
函数不能同名 支持函数重载
无默认参数 支持默认参数
用 int 表示真假 有内置 bool
字符串用 char[] 有 std::string
面向过程为主 支持面向对象(还没学到)

现在接触到的还只是语法层面的差异,C++ 真正不同于 C 的地方(类、对象、模板、STL)还没碰到。但这几个基础点已经让我感觉到:C++ 在保持 C 的表达能力的同时,在一些常见场景下确实更好用一些。


十、我的第一次练习:三个小程序的完整记录

自包含定义:本节记录的是学习过程中的三个动手练习,每个练习说明"做了什么、想验证什么、代码、运行结果、如何验证"。所有运行结果均为本地编译后实际输出,未虚构。

练习一:学生信息录入

  • 做了什么:写一个程序,依次询问姓名、年龄、身高、专业,然后格式化输出
  • 想验证什么:cin 能不能读不同类型;std::string 是否比 char[] 好用
  • 代码:v1_0.cpp(见 3.1 节)
  • 验证方法:

g++ -std=c++17 v1_0.cpp -o v1_0
./v1_0

  • 运行结果:按预期输入后正常输出,几行对齐也符合预期
  • 发现:cin 读字符串时遇到空格就停,姓名中间有空格的话只读到前面一半。要读完整行得用 getline

练习二:string 的长度和拼接

  • 做了什么:定义两个 string,打印它们的 length(),然后拼接后输出
  • 想验证什么:C++ 的字符串操作是否比 C 简单
  • 代码:cout_cin.cpp 最后一段

// cout_cin.cpp(节选)
#include <iostream>
#include <string>

int main()
{
std::string name("xiaosu"), city("nanjing");
std::cout << name.length() << std::endl; // 输出 6
std::cout << city.length() << std::endl; // 输出 7

std::string result = name + " " + city;
std::cout << result << std::endl; // 输出 xiaosu nanjing

std::string name1("xiao su");
std::cout << "请输入:";
std::getline(std::cin, name1); // 读整行
std::cout << name1 << std::endl; // 输出 xiao su
std::cout << name1.length() << std::endl; // 输出 7
return 0;
}

  • 验证方法:

g++ -std=c++17 cout_cin.cpp -o cout_cin
echo "xiao su" | ./cout_cin

  • 运行结果:name.length() 输出 6,city.length() 输出 7;拼接后输出 xiaosu nanjing;输入 xiao su 后 length() 返回 7
  • 发现:+ 直接拼接字符串比 strcat 舒服。用 cin >> 只能读到 4,用 getline 能读到 7

练习三:函数重载和引用传参

  • 做了什么:写了三个同名的 calculate 函数,分别处理两参数 int、两参数 double、三参数 int
  • 想验证什么:编译器能不能正确挑出重载的版本;引用传参是不是真的能省掉 & 和 *
  • 代码:namespace_&.cpp 里的 calculate 部分(见 8.1 节)
  • 验证方法:

g++ -std=c++17 namespace_\\&.cpp -o ns_ref
./ns_ref
# 预期输出:
# 15
# 15.9
# 35

  • 运行结果:三次调用分别输出 15、15.9、35,都跟预期一致
  • 发现:重载版本多的时候,光看调用不太容易判断到底调了哪个。addTen 那个引用练习里,一改形参外部变量就变,第一次看到还挺新奇的

十一、学到这里之后的感受

11.1 哪些好理解、哪些容易混淆

比较好理解的部分:

  • 基本结构、变量、分支循环、函数:和 C 几乎一样
  • cout、cin:用法直观,比格式串省心
  • bool:C 里就该有这个,终于不用拿 int 当布尔用

容易混淆的部分:

  • 引用的 & 和取地址的 & 写法一样,一开始会看错
  • using namespace std; 到底该不该加,我到现在还是看场景决定
  • 函数重载调用多了以后不容易看出来调的是哪个版本

11.2 引用 vs 指针:适用与不适用

场景优先用引用优先用指针
函数参数传递,不修改原变量 ✅ 用 const T&
函数参数传递,需要修改原变量 ✅ 用 T& ⚠️ 也可,但写法繁琐
需要表示"可能为空"的参数 ❌ 引用不能为空 ✅ 用 T* 并检查
需要重新指向别的对象 ❌ 引用绑定后固定 ✅ 用 T*
数组遍历、动态内存管理 ❌ 不适合 ✅ 标准做法
返回内部大对象且不希望拷贝 ✅ 返回 const T& ⚠️ 需保证生命周期

11.3 C 基础带来的帮助

  • 变量、指针、函数这些概念在 C++ 里都还在,不需要重新学
  • 遇到"值传递 vs 地址传递"这种问题时,C 里建立的理解可以直接迁移
  • 写 main、#include、编译运行这些流程不用重新熟悉

11.4 目前还没掌握的部分

  • 类的定义和使用(只听说过,还没写过一个类)
  • 构造函数、析构函数这些概念还完全没概念
  • 模板、STL 容器只知道有这些东西,细节不知道
  • 运算符重载、继承、多态都还停留在"听说过"的阶段

十二、常见问题(FAQ):从 C 到 C++ 入门最常遇到的三个坑

自包含定义:本节 FAQ 覆盖从 C 转 C++ 入门阶段最容易卡住的三个问题——cin 与 getline 的选择、using namespace std; 的使用场景、引用与指针的优先选择。每个问题都对应初学者在真实写代码时的高频困惑。

Q1:cin >> 和 getline 到底怎么选?

看是否包含空格:

  • 不含空格(如姓名、工号、单个单词):用 cin >>,简单直接
  • 含空格(如整句话、带空格的姓名):用 getline(cin, str),会读到换行符之前

一个常见的坑:cin >> 和 getline 混用时,cin >> 会在缓冲区留下换行符,导致后面的 getline 直接读到空串。解决办法是在两者之间加一句 cin.ignore() 清掉换行符:

int age;
std::string line;
std::cin >> age;
std::cin.ignore(); // 清掉残留的换行符
std::getline(std::cin, line); // 现在能正确读到整行

Q2:using namespace std; 能不能在项目里用?

可以,但不推荐在头文件或大型项目中使用。原因:

  • 头文件里绝对不要用:任何 #include 了该头文件的源文件都会被污染,容易出问题
  • 单文件小练习:无所谓,图省事可以加
  • 团队协作/大项目:建议用 using std::cout; 明确引入需要的名字,或直接写 std:: 前缀

我现在的做法是:单文件小练习加,稍大一点的代码老老实实写 std::。

Q3:引用和指针什么时候优先用哪个?

核心判断依据:

  • 参数传递,不修改原变量 → 用 const T&(避免拷贝,且不会误改)
  • 参数传递,需要修改原变量 → 用 T&(写法最简洁)
  • 返回值可能为空 → 用指针(引用不能为空)
  • 需要在函数里切换指向的对象 → 用指针(引用绑定后不能改)
  • 数组遍历、动态内存 → 用指针(引用不适合这些场景)

简单记:能用引用就用引用,需要"可为空"或"改指向"时用指针。


十三、本篇小结

这篇文章记录的是我从 C 转向 C++ 的第一小段学习过程:最基础的程序结构、cout 和 cin、namespace、引用、bool、默认参数、函数重载,以及三个小练习。没有虚构任何没跑过的结果,也没有把还没学的内容写成已经掌握的。

从熟悉到陌生,最明显的过渡点是引用和函数重载——这两个东西在 C 里根本不存在,第一次用的时候确实有点不顺手。但只要理解"引用是别名"、"重载是按参数挑函数"这两句话,用起来其实还好。


时效声明 本文代码验证于 g++ 13.2 + C++17 标准(Windows 11 + MinGW),验证日期 2026-09-22。所有代码均在本地编译通过,所有运行结果均为实际输出。示例代码仅覆盖 C++ 基础语法层,不涉及 C++11/14/17/20/23 的现代特性(如 auto、lambda、std::unique_ptr 等)。

多平台分发入口 本文首发于 CSDN,转载请注明出处。 知乎专栏「C/C++ 学习笔记」 | 微信公众号「从零开始的编程笔记」


你也是从 C 转 C++ 的吗?学习过程中觉得最别扭的是哪个点——是引用的 & 和取地址符长得一样,还是函数重载看起来"不太可信"?欢迎在评论区交流你的入门经历。

相关阅读:

  • C++ 类与对象入门:从结构体到 class 的第一步(下一篇,正在整理中)

接下来继续学习 C++ 的类与对象。

赞(0)
未经允许不得转载:171主机测评 » 从 C 到 C++ 怎么入门?基础语法与第一次练习
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址