news 2026/6/6 12:55:26

一篇搞定C++基础语法与核心机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一篇搞定C++基础语法与核心机制

个人专栏:《数据结构-初阶》《经典OJ题目》《C语言》《小白算法成长录》

欢迎大佬交流

本文代码已同步GitHub:GitHub - Stellen-z/DailyCode: pracetice · GitHub

一、从C语言到C++

1、为什么学习C++

C++是在C语言基础上发展出来的语言,它不仅保留了C的:

  • 高性能
  • 直接操作内存的能力
  • 适合底层开发的特性

还增加了:

  • 面向对象编程(OOP)
  • 泛型编程(模板)
  • 标准模板库(STL)

简单理解:

C是“工具”,C++是“工具箱 + 设计系统”

学习C++的意义是:

  • 从“过程式思维” → “结构化 + 对象化思维”
  • 从“写单个函数” → “设计完整程序结构”
  • 为后续高阶数据结构与算法打基础

2、C和C++的区别

C语言:面向过程编程

C语言以“函数”为核心,把问题拆解为一个个步骤:

  • 程序 = 函数 + 数据
  • 强调“怎么做”(过程)

C++:多范式语言(面向对象为主)

C++支持:

  • 面向过程(兼容C)
  • 面向对象(OOP)
  • 泛型编程(模板)

更强调:

“谁来做这件事”(对象)

C语言不支持:

  • 没有类(class)
  • 没有对象
  • 没有封装 / 继承 / 多态

C++支持:

  • class / struct(增强版)
  • 封装(封装数据和方法)
  • 继承(代码复用)
  • 多态(接口统一)

3、第一个C++程序

首先明确C++兼容C绝大多数的语法,因此我们先在C++环境中编译C代码:

#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }

程序正常运行!

当然,C++有自己的输入输出,肯定不会像C语言这样写

#include <iostream> using namespace std; int main() { cout << "Hello World!" << endl; return 0; }

不知道各位小伙伴用C++/Java第一次写出Hello World时,和第一次用C语言写出来的Hello World有什么不同的感觉呢?

下面我们就从C++的第一个程序开始学习基础语法!

二、C++基础语法

1、namespace

我们先来看一段C语言程序:

#include <stdio.h> #include <stdlib.h> int rand = 10; int main() { //error C2365: “rand”: 重定义;以前的定义是“函数” printf("%d\n", rand); return 0; }

直接编译错误!

报错原因是重定义,当我们想打印 rand 时,在C语言的编译环境下、

rand是一个函数,而我们在全局变量中又定义了rand,导致rand的重定义!

那么在C++中,我们该怎么避免这种情况呢?
答案就是使用 namespace--命名空间

namespace 的定义

● 定义命名空间,需要使用namespace 关键字,后面跟命名空间的名字,然后加上一对{ },{ }中即为命名空间的成员;可以定义变量、函数、类等;

● namespace 本质是定义一个域,这个域跟全局域各自独立,而不同的域可以定义同名变量,因此基本解决了命名冲突

● C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑;

因此,有了域隔离,名字冲突就解决了;

局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。

● namespace 只能定义在全局,当然也可以嵌套定义

● 在项目工程多文件中,定义的同名 namespace 会认为是一个 namespace ,不会冲突

● C++标准库都放在一个叫 std(standard) 的命名空间中

下面我们来定义上述的例子

//1.命名空间定义 #include <stdio.h> #include <stdlib.h> namespace stn { //定义变量 int rand = 20; //定义函数 int Add(int x, int y) { return x + y; } } int main() { printf("%p\n", rand);//默认访问rand函数指针 printf("%d\n", stn::rand);//访问stn命名空间中的rand return 0; }

//2.命名空间的嵌套 #include <stdio.h> #include <stdlib.h> namespace mn { //pq命名空间 namespace pq { int rand = 1; int Add(int x, int y) { return x + y; } } //xy命名空间 namespace xy { int rand = 1; int Add(int x, int y) { return x + y; } } } int main() { printf("%d\n", mn::pq::rand); printf("%d\n", mn::xy::rand); printf("%d\n", mn::pq::Add(1, 2)); printf("%d\n", mn::xy::Add(1, 2)); return 0; }

//多文件可以重复定义同名的namespace,他们会默认合并到一起 //Stack.h #pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> namespace stn { // 支持动态增长的栈 typedef int STDataType; typedef struct Stack { STDataType* _a; int _top; // 栈顶 int _capacity; // 容量 }Stack; // 初始化栈 void StackInit(Stack* ps); // 入栈 void StackPush(Stack* ps, STDataType data); // 出栈 void StackPop(Stack* ps); // 获取栈顶元素 STDataType StackTop(Stack* ps); // 获取栈中有效元素个数 int StackSize(Stack* ps); // 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 bool StackEmpty(Stack* ps); // 销毁栈 void StackDestroy(Stack* ps); }
//Stack.cpp #include "Stack.h" namespace stn { // 初始化栈 void StackInit(Stack* ps) { assert(ps); ps->_a = NULL; ps->_capacity = ps->_top = 0; } // 入栈 void StackPush(Stack* ps, STDataType data) { assert(ps); if (ps->_capacity == ps->_top) { int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity; STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity); if (tmp == NULL) { perror("realloc failed!\n"); exit(1); } ps->_a = tmp; ps->_capacity = newcapacity; } ps->_a[ps->_top++] = data; } // 出栈 void StackPop(Stack* ps) { assert(ps); assert(ps->_top > 0); ps->_top--; } // 获取栈顶元素 STDataType StackTop(Stack* ps) { assert(ps); assert(ps->_top > 0); return ps->_a[ps->_top - 1]; } // 获取栈中有效元素个数 int StackSize(Stack* ps) { assert(ps); return ps->_top; } // 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 bool StackEmpty(Stack* ps) { assert(ps); return ps->_top == 0; } // 销毁栈 void StackDestroy(Stack* ps) { free(ps->_a); ps->_a = NULL; ps->_capacity = ps->_top = 0; } }
//Test.cpp #include "Stack.h" //定义全局的Stack typedef struct Stack { int a[10]; int top; }ST; void STInit(ST* ps) {}; void STPush(ST* ps, int x) {}; int main() { //调用全局的栈 ST st1; STInit(&st1); STPush(&st1, 1); STPush(&st1, 2); STPush(&st1, 3); printf("%d\n", sizeof(st1));//固定10个int类型,共44字节 //调用stn的栈 stn::Stack st2; printf("%d\n", sizeof(st2));//一个x64环境下指针,两个int类型,共16字节 stn::StackInit(&st2); stn::StackPush(&st2, 1); stn::StackPush(&st2, 2); stn::StackPush(&st2, 3); }

namespace 的使用

当编译器在查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间中查找;

因此下面的程序会报错

#include <stdio.h> namespace stn { int a = 0; int b = 1; } int main() { //error C2065: “a”: 未声明的标识符 printf("%d\n", a); return 0; }

要使用命名空间中定义和变量、函数,有三种方式:

a、指定命名空间,项目中推荐这种方式

int main() { printf("%d\n", stn::a); return 0; }

b、using将命名空间中某个成员展开,项目中经常访问且不存在冲突的成员推荐这种方式

using stn::a; int main() { printf("%d\n", a); printf("%d\n", stn::b); return 0; }

c、展开命名空间全部成员,项目不推荐,冲突风险很大,日常练习可以使用

using namespace stn; int main() { printf("%d\n", a); printf("%d\n", b); return 0; }

2、输入输出

在C语言中,包含输入输出的头文件是<stdio.h>

而在C++中,包含输入输出的头文件是<iostream>

<iostream>是 Input Output Stream的缩写,是标准的输入,输出流库,定义了标准的输入,输出对象;

而在前面我们说过,std这个命名空间里面包含cin 和 cout

std::cin 是 istream类的对象,主要面向的是窄字符(narrow characters)的标准输入流,那么宽字符对应的就是wcin;

std::cout 是 ostream 类的对象,主要面向窄字符的标准输出,那么宽字符对应的就是wcout;

<<是流插入运算符,>>是流提取运算符

std::endl 是一个函数,流插入输出时,相当于一个换行符加刷新缓冲区;

使用C++输入输出更方便,不需要printf/scanf那样需要手动指定格式,C++的输入输出可以自动识别变量类型

cout/cin/endl等都属于C++标准库,C++标准库都放在std的命名空间中,因此要通过命名空间来使用

因此,在日常练习中我们可以 using namespace std,实际项目开发中不能直接展开

由于C++兼容C大部分语法,因此在编译时,会一并编译C语言的代码

这就导致有时C++的效率会降低

比如当需要处理大量输入输出时,为了提高效率,我们通常会加上下面这三行代码

#include <iostream> int main() { std::ios_base::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); return 0; }

三行代码的作用:

  1. std::ios_base::sync_with_stdio(false);
    取消 C++ 标准流(cin/cout)与 C 标准流(stdio)之间的同步;默认情况下两者同步,以保证混用时的顺序正确,但会带来额外开销。关闭同步后,cin/cout效率大幅提升,但不能再混用printf/scanf,否则会导致未定义行为。

  2. std::cin.tie(nullptr);
    解除cincout的绑定。默认情况下,每次执行cin前会自动刷新cout,解除绑定可减少不必要的刷新开销。

  3. std::cout.tie(nullptr);
    解除cout与其他流的绑定(通常无额外绑定,但保持写法对称或防止未来修改)。

三、函数增强

1、缺省参数

缺省参数的定义:允许在函数声明时为参数指定默认值;调用时若省略该实参,则自动使用默认值。

缺省分为全缺省、半缺省;

全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值;

注:C++规定半缺省参数必须从右向左依次连续缺省,不能间隔跳跃给缺省值

函数定义和声明分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值

我们来看下面的例子就能明白

a、缺省参数的使用

#include <iostream> using namespace std; void fun(int a = 1) { cout << a << endl; } int main() { fun(); //没有传参时,使用参数的默认值 fun(10); //传参时,使用指定的实参 }

b、缺省参数的分类

#include <iostream> using namespace std; //全缺省 void Func1(int a = 1,int b = 2,int c = 3) { cout << a << " " << b << " " << c << endl; } //半缺省 void Func2(int a, int b = 2, int c = 3) { cout << a << " " << b << " " << c << endl; } int main() { Func1(); //1 2 3 Func1(10); //10 2 3 Func1(10,20); //10 20 3 Func1(10,20,30); //10 20 30 Func2(100); //100 2 3 Func2(100,200); //100 200 3 Func2(100,200,300); //100 200 300 }

2、函数重载

C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同:可以是参数个数不同或者是类型不同!

注:C语言是不支持同一作用域出现同名函数的

我们通过代码来观察

#include <iostream> using namespace std; namespace stn { //1.参数类型不同 int Add(int x, int y) { cout << "Add(int x, int y)" << endl; return x + y; } double Add(double x, double y) { cout << "Add(double x, double y)" << endl; return x + y; } //2.参数个数不同 void func(int a) { cout << "func(int a)" << endl; } void func(int a, int b) { cout << "func(int a,int b)" << endl; } //3.参数类型顺序不同 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; } } int main() { stn::Add(1, 2); stn::Add(1.1, 2.2); stn::func(1); stn::func(1,2); stn::f(1, 'x'); stn::f('x', 1); return 0; }

既然参数类型、参数个数、参数顺序都会构成函数重载,那么函数返回值是否会构成重载呢?

我们来试一下

#include <iostream> using namespace std; int f() { cout << "f()" << endl; return 1; } //error C2556: “void f(void)”: 重载函数与“int f(void)”只是在返回类型上不同 void f() { cout << "f()" << endl; } int main() { f(); f(); return 0; }

编译失败,编译器在调用时不知道到底调用哪一个函数!

最后我们来看下面这组代码

#include <iostream> using namespace std; void f() { cout << "f()" << endl; } void f(int a = 10) { cout << "f(int a = 10)" << endl; } int main() { f();//error C2668: “f”: 对重载函数的调用不明确 f(10); return 0; }

首先来看,这两个函数是否构成函数重载?

答案是构成!

那为什么会报错呢?

当全缺省函数和无参函数都存在时,如果调用函数时没有进行传参,就会引发歧义,编译器不知道到底该调哪个函数!

3、inline

a、内联函数的定义

用 inline 修饰的的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,提高效率!

b、内联函数的使用

下面我们来写一个简单的内联函数

#include <iostream> using namespace std; inline int Add(int x, int y) { int ret = x + y; ret += 1; return ret; } int main() { int ret = Add(1, 2); cout << ret << endl; return 0; }

通过调试来观察内联函数是否展开

发现框选部分并没有call/ret指令,即没有通过call指令进行调用,直接将代码复制过来!

最后要注意,inline不建议声明和定义分离到两个文件,分离会导致连接错误;

因为inline被展开,没有函数地址,链接时会出现报错!

c、内联函数与宏函数的区别

C++设计内联函数的目的就是为了替代C语言的宏函数

C语言的宏函数也会在预处理时替换展开,但是宏函数很复杂,并且由于直接替换,不方便调试;

下面我们来简单实现一个加法宏函数

//请问哪个宏函数是正确的? #define Add(int a,int b) return a + b; #define Add(a,b) return a + b; #define Add(a,b) (a + b)

答案是上述三个均是错误的宏函数

宏函数的本质是进行替换;

首先来看第一个

//#define Add(int a,int b) return a + b; int ret = Add(1, 2); //展开之后变成 int ret = return 1 + 2;; //1.return不能出现在赋值表达式 //2.末尾有两个分号

接着来看第二个

//#define Add(a,b) a + b; int ret = Add(1, 2); //展开之后变成 int ret = 1 + 2;; //此时影响不大,结果正常 cout << Add(1, 2) * 3 << endl; //这样展开之后呢? //cout << 1 + 2 * 3 ; << endl; //显然1.没有括号 2.分号多余

继续来看第三个

//#define Add(a,b) (a + b) cout << Add(1, 2) << endl; // -> cout << (1 + 2) << endl; //此时没有问题 cout << Add(1 & 2, 1 | 2) << endl; // -> cout << (1 & 2 + 1 | 2) << endl; //显然错误,+ 的运算符优先级高于 & ,导致计算顺序错误

最后来看正确写法

#define Add(a,b) ((a) + (b));

四、引用

1、引用的概念和定义

引用的基本概念

引用是C++中一种特殊的变量类型,本质上是已存在变量的别名;

通过引用可以直接操作原变量,无需拷贝数据;

引用在定义时必须初始化,且一旦绑定到某个变量后无法更改指向。

引用的定义语法

引用的定义方式为在变量类型后加 & 符号:

  • 类型匹配:引用必须与原变量类型一致(const引用除外)。
  • 必须初始化:定义时需直接绑定到一个已存在的变量。
  • 无独立内存:引用不占用额外存储空间,仅作为别名存在。

我们尝试来使用引用

#include <iostream> using namespace std; int main() { int a = 1; int& b = a; int& c = a; int& d = a; cout << &a << endl; cout << &b << endl; cout << &c << endl; cout << &d << endl; return 0; }

2、const引用

a、引用const对象必须要用const来引用;const引用也可以引用普通对象,那么就会造成权限缩小的问题;

#include <iostream> using namespace std; int main() { const int a = 1; //权限放大:error C2440: “初始化”: 无法从“const int”转换为“int &” //int& ra = a; //正确引用 const int& ra = a; //error C3892: “ra”: 不能给常量赋值 //ra++; //权限缩小 int b = 10; const int& rb = b; //error C3892: “rb”: 不能给常量赋值 //rb++; return 0; }

b、临时对象是指编译器需要一个空间暂存表达式的求职结果时,临时创建的一个未命名对象;

而在类型转换中就会产生临时对象,而临时对象具有常性,此时就需要使用常引用!

#include <iostream> using namespace std; int main() { int a = 10; //常量值用常引用 const int& x = 20; //error C2440: “初始化”: 无法从“int”转换为“int &” //int& ret = a * 2; //临时对象用常引用 const int& ret = a * 2; double d = 11.22; //error C2440: “初始化”: 无法从“double”转换为“int &” //int& rd = d; //类型转换会产生临时对象,要用常引用 const int& rd = d; return 0; }

3、指针和引用

引用与指针的区别

  • 初始化要求:引用必须初始化,指针可以为空(nullptr
  • 语法概念上:引用是一个变量的别名,不开空间,指针是存储一个变量的地址,要开空间
  • 可修改性:引用绑定后不可更改,指针可以重新指向其他地址
  • 访问语法:引用直接使用变量名操作,指针需解引用(*ptr
  • 大小上:sizoef中引用的大小取决于引用类型的大小,但指针始终是地址空间所占字节个数
  • 安全性:引用不存在“空引用”问题,指针可能悬空

五、空指针

1、NULL

在C语言中,NULL实际上是一个宏;

在stddef.h文件中可以看到下面代码:

#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif

C++中NULL被定义成0,C语言中是一个类型为void *、值为零的空指针常量;

我们来看下面代码

#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 x) f((int*)0); f((int*)NULL);//调用f(int* ptr) //error C2665: “f”: 没有重载函数可以转换所有参数类型 //f((void*)NULL); return 0; }

会发现即使传入NULL,本想调用指针版本的,却仍调用整数版本!导致出现很多歧义;

最后由于C++禁止void*隐式类型转换为其他指针类型,因此会报错

2、nullptr

在C++中,为了避免函数调用歧义的问题,引入了新的关键字 nullptr;

nullptr可以转换成任意其他类型的指针;

因此,使用nullptr可以避免指针类型转换的问题;

nullptr只能隐式的转换成指针类型,而不能被转换成整数类型

如果觉得有帮助,可以关注 GitHub 项目持续更新:GitHub - Stellen-z/DailyCode: pracetice · GitHub

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 12:53:14

MarkDownload终极指南:5分钟掌握网页转Markdown的完整技巧

MarkDownload终极指南&#xff1a;5分钟掌握网页转Markdown的完整技巧 【免费下载链接】markdownload A Firefox and Google Chrome extension to clip websites and download them into a readable markdown file. 项目地址: https://gitcode.com/gh_mirrors/ma/markdownloa…

作者头像 李华
网站建设 2026/6/6 12:52:14

ZLUDA终极指南:如何在非NVIDIA GPU上高效运行CUDA应用

ZLUDA终极指南&#xff1a;如何在非NVIDIA GPU上高效运行CUDA应用 【免费下载链接】ZLUDA CUDA on non-NVIDIA GPUs 项目地址: https://gitcode.com/GitHub_Trending/zl/ZLUDA 你是否曾经面临这样的困境&#xff1a;手头只有AMD或Intel显卡&#xff0c;却需要运行那些依…

作者头像 李华
网站建设 2026/6/6 12:51:38

PCB设计进阶:从引脚间距到三维安装的DFM实战指南

1. 项目概述&#xff1a;从“能放下”到“能焊好、能散热、能过检”在电子硬件开发领域&#xff0c;PCB设计是连接原理图与物理实物的桥梁。很多工程师&#xff0c;尤其是刚入行的朋友&#xff0c;常常把重心放在原理的正确性和功能的实现上&#xff0c;认为只要元器件在软件里…

作者头像 李华
网站建设 2026/6/6 12:51:33

DeepSeek R1安全审计:逻辑断点、数据库裸奔与 Jailbreak 根因分析

1. 项目概述&#xff1a;当一个AI模型主动递给你一把万能钥匙“如果AI安全是一场捉迷藏游戏&#xff0c;DeepSeek R1不是躲在柜子里&#xff0c;而是站在玻璃房中央&#xff0c;一边挥手一边把藏身点的3D建模图发到你邮箱。”这句话不是修辞&#xff0c;是实测结论。我过去三年…

作者头像 李华