一、 C++的第一个程序
C++兼容C语言绝大多数的语法,所以C语言实现的hello world依旧可以运行。C++中需要把定义文件的后缀改为.cpp,编译器看到是.cpp就会调用C++编译器编译,Linux下要用g++编译,不再是gcc。
1.1 C风格的Hello World
cpp
// test.cpp
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
1.2 C++风格的Hello World
C++有一套自己的输入输出,严格说C++版本的hello world应该是这样写的:
cpp
// test.cpp
#include<iostream>
using namespace std;
int main()
{
cout << "hello world\n" << endl;
return 0;
}
二、 命名空间
2.1 namespace的价值
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
C语言项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入namespace就是为了更好的解决这样的问题:
cpp
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
// 编译报错: error C2365: "rand": 重定义;以前的定义是"函数"
printf("%d\n", rand);
return 0;
}
报错核心原因:
stdlib.h 头文件中自带rand()函数(生成随机数的库函数),你定义的全局变量int rand = 10和库函数rand重名,C语言不允许变量与函数同名,触发重定义报错。
细节拆解(3个关键知识点)
-
头文件引入影响:stdlib.h 是C标准库头文件,引入后会暴露rand()函数(声明:int rand(void);),作用域覆盖整个文件
-
命名冲突规则:C语言中,变量名和函数名属于同一命名空间,不能重复定义/声明
-
全局变量特性:你定义的int rand是全局变量,作用域从定义处到文件结束,和库函数rand的作用域完全重叠,冲突必报错
2.2 namespace的定义
• 定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
• namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand不在冲突了。
• C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
• namespace只能定义在全局,当然他还可以嵌套定义。
• 项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。
• C++标准库都放在一个叫std(standard)的命名空间中。
2.2.1 正常的命名空间定义
cpp
#include <stdio.h>
#include <stdlib.h>
// 1. 正常的命名空间定义
// gxy是命名空间的名字,一般开发中是用项目名字做命名空间名。
namespace gxy
{
// 命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
// 这里默认是访问的是全局的rand函数指针
printf("%p\n", rand);
// 这里指定gxy命名空间中的rand
printf("%d\n", gxy::rand);
return 0;
}
细节拆解如下:
-
核心原理:namespace(命名空间)是C++特性,用来隔离命名,避免全局命名冲突,gxy空间里的rand和stdlib.h的rand函数互不干扰
-
访问规则:不加命名空间默认访问全局(这里是stdlib.h的rand函数,打印出函数地址);加gxy:: 才访问自定义命名空间里的int型rand变量
-
兼容性提醒:你的代码后缀如果是.c,编译器按C语言编译会报错;要改成.cpp,按C++编译才能运行
2.2.2 命名空间可以嵌套
cpp
namespace gxy
{
// 小王
namespace xw
{
int rand = 1;
int Add(int left, int right)
{
return left + right;
}
}
// 小张
namespace xz
{
int rand = 2;
int Add(int left, int right)
{
return (left + right)*10;
}
}
}
int main()
{
printf("%d\n", gxy::xw::rand);
printf("%d\n", gxy::xz::rand);
printf("%d\n", gxy::xw::Add(1, 2));
printf("%d\n", gxy::xz::Add(1, 2));
return 0;
}
核心3点解析:
-
嵌套逻辑:gxy是父命名空间,xw/xz是子命名空间,层级访问用::串联,互不干扰
-
冲突隔离:xw和xz里的rand、Add同名完全不冲突,精准访问对应域的成员
-
编译要求:后缀必须是.cpp,按C++编译,.c后缀会报语法错
2.2.3 多文件中可以定义同名namespace
多文件中可以定义同名namespace,他们会默认合并到一起,就像同一个namespace一样。
多文件可以定义同名namespace,编译器会自动合并成同一个,核心用来拆分代码到多文件,不冲突还能统一管理。
极简示例(验证多文件同名namespace)
新建3个文件,直观验证合并效果
① file1.h(声明)
cpp
#pragma once
namespace gxy{
void func1(); // 声明func1
}
② file2.h(声明)
cpp
#pragma once
namespace gxy{
void func2(); // 同名namespace,声明func2
}
③ main.cpp(使用)
cpp
#include "file1.h" // 导入gxy命名空间中func1的声明
#include "file2.h" // 导入gxy命名空间中func2的声明
// 同名namespace gxy,编译器会和头文件里的gxy合并成同一个
namespace gxy{
// 实现func1,归属gxy命名空间,和file1.h里的声明匹配
void func1(){printf("func1\n");}
// 实现func2,归属gxy命名空间,和file2.h里的声明匹配
void func2(){printf("func2\n");}
}
int main(){
// 访问合并后gxy命名空间里的func1,调用成功
gxy::func1();
// 访问合并后gxy命名空间里的func2,调用成功
gxy::func2();
return 0;
}
- 本质原理(关键)
同一个工程里,所有同名namespace(比如你所有文件里的gxy),会被编译器视为同一个命名空间,里面的成员(结构体/函数/变量)会合并到一起,不会冲突。
- 代码实战对应(一看就懂)
多文件同名gxy,就是标准用法:
• Stack.h/Stack.cpp → 把栈相关的ST、STPush等放进gxy
• Queue.h/Queue.cpp → 把队列相关的Queue、QueuePush等放进gxy
• 编译器最终合并成1个gxy,里面同时有栈+队列的所有成员,访问都用gxy::xxx
- 核心规则(避坑必记)
合并规则:同名namespace不管在多少文件,成员都合并,成员名不能重复(比如不能在两个文件的gxy里都定义int rand)
声明与实现分离:头文件(.h)放namespace里的声明(结构体、函数声明),源文件(.cpp)放同名namespace里的实现,必须一致
头文件防护:必须加#pragma once,防止头文件重复包含导致成员重复声明报错
- 实用好处(为什么要这么用)
拆分代码:把栈、队列等不同模块拆到多文件,代码整洁,维护方便
全局隔离:所有模块共用一个命名空间,对外只暴露一个gxy::,避免和其他代码冲突
扩展方便:后续加链表、树等模块,直接放进同名namespace,无需改原有代码
- 注意坑点
• 同名namespace里,不能有同名成员(比如两个文件都定义gxy::ST)
• 源文件实现函数时,必须写在同名namespace里,否则不属于该命名空间
• 头文件一定要加防护,否则重复包含会报"重复声明"
Stack.h
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
// 命名空间gxy,隔离栈相关定义,避免命名冲突
namespace gxy
{
typedef int STDataType; // 栈存储数据的类型,方便后续修改
// 栈结构体(动态数组实现)
typedef struct Stack
{
STDataType* a; // 指向动态数组的指针,存储栈元素
int top; // 栈顶标记(初始0),指向栈顶元素的下一个位置
int capacity; // 栈的容量(数组最大能存的元素个数)
}ST;
void STInit(ST* ps, int n); // 栈初始化,n为初始容量
void STDestroy(ST* ps); // 栈销毁,释放内存,防止泄漏
void STPush(ST* ps, STDataType x); // 入栈(压栈),往栈顶加元素
void STPop(ST* ps); // 出栈(弹栈),删除栈顶元素(不返回)
STDataType STTop(ST* ps); // 获取栈顶元素的值
int STSize(ST* ps); // 获取栈中有效元素个数
bool STEmpty(ST* ps); // 判断栈是否为空,空返回true
}
Stack.cpp
cpp
#include"Stack.h"
namespace gxy
{
// 栈初始化函数
void STInit(ST* ps, int n)
{
assert(ps); // 断言:防止传入NULL指针导致崩溃
// 按初始容量n申请动态内存
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
// 内存申请失败且初始容量非0,打印错误信息
if(ps->a == NULL && n != 0)
{
perror("malloc fail");
return;
}
ps->top = 0; // 栈顶初始化为0,指向栈顶下一位
ps->capacity = n; // 栈容量赋值为初始n
}
// 入栈函数(核心:满了自动扩容)
void STPush(ST* ps, STDataType x)
{
assert(ps); // 防止传入NULL
// 栈满了,触发扩容
if (ps->top == ps->capacity)
{
printf("扩容\n");
// 扩容规则:原容量0则开4个,否则2倍扩容
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// 重新申请更大内存,realloc可复用原内存空间
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
if (tmp == NULL) // 扩容失败打印错误
{
perror("realloc fail");
return;
}
ps->a = tmp; // 指向新申请的内存
ps->capacity = newcapacity; // 更新栈容量
}
ps->a[ps->top] = x; // 元素存入栈顶位置
ps->top++; // 栈顶后移,更新位置
}
// 栈销毁函数(必须释放动态内存)
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a); // 释放动态数组内存
ps->a = NULL; // 指针置空,避免野指针
ps->top = ps->capacity = 0; // 栈状态重置
}
// 出栈函数(只删除栈顶,不返回元素)
void STPop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps)); // 断言:空栈不能出栈
ps->top--; // 栈顶前移,相当于删除栈顶元素(空间复用)
}
// 获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps)); // 空栈无栈顶,断言保护
return ps->a[ps->top - 1]; // top指向栈顶下一位,所以取top-1位置
}
// 获取栈有效元素个数
int STSize(ST* ps)
{
assert(ps);
return ps->top; // top初始0,每入栈一次+1,值就是元素个数
}
// 判断栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0; // top为0表示无有效元素,栈空
}
}
Queue.h
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
// 命名空间gxy,隔离队列相关定义,避免命名冲突
namespace gxy
{
typedef int QDataType; // 队列存储数据类型,方便修改
// 队列的链表节点结构体
typedef struct QueueNode
{
QDataType val; // 节点存储的数据
struct QueueNode* next; // 指向下一个节点的指针,构成链表
}QNode;
// 队列管理结构体(双指针+计数,方便操作)
typedef struct Queue
{
QNode* phead; // 队头指针,指向链表头(出队从这里删)
QNode* ptail; // 队尾指针,指向链表尾(入队从这里加)
int size; // 队列有效元素个数,不用遍历统计
}Queue;
void QueueInit(Queue* xw); // 队列初始化
void QueueDestroy(Queue* xw); // 队列销毁,释放所有节点内存
void QueuePush(Queue* xw, QDataType x); // 入队(队尾插入)
void QueuePop(Queue* xw); // 出队(队头删除)
QDataType QueueFront(Queue* xw); // 获取队头元素
QDataType QueueBack(Queue* xw); // 获取队尾元素
bool QueueEmpty(Queue* xw); // 判断队列是否为空
int QueueSize(Queue* xw); // 获取队列有效元素个数
}
Queue.cpp
cpp
#include"Queue.h"
namespace gxy
{
// 队列初始化
void QueueInit(Queue* xw)
{
assert(xw); // 防止传入NULL指针
xw->phead = NULL; // 队头初始为空
xw->ptail = NULL; // 队尾初始为空
xw->size = 0; // 初始元素个数为0
}
// 队列销毁(必须遍历释放所有节点,避免内存泄漏)
void QueueDestroy(Queue* xw)
{
assert(xw);
QNode* cur = xw->phead; // 从队头开始遍历
while(cur) // 遍历所有节点
{
QNode* next = cur->next; // 先保存下一个节点地址
free(cur); // 释放当前节点
cur = next; // 移动到下一个节点
}
xw->phead = xw->ptail = NULL; // 头尾指针置空
xw->size = 0; // 元素个数重置
}
// 入队(队尾插入,核心)
void QueuePush(Queue* xw, QDataType x)
{
assert(xw);
// 申请新节点内存
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if(newnode == NULL) // 内存申请失败打印错误
{
perror("malloc fail");
return;
}
newnode->val = x; // 新节点赋值
newnode->next = NULL;// 新节点是队尾,next置空
if(xw->ptail == NULL) // 情况1:队列是空队列
{
xw->phead = xw->ptail = newnode; // 头尾都指向新节点
}
else // 情况2:队列已有元素
{
xw->ptail->next = newnode; // 原队尾next指向新节点
xw->ptail = newnode; // 更新队尾指针为新节点
}
xw->size++; // 元素个数+1
}
// 出队(队头删除,核心)
void QueuePop(Queue* xw)
{
assert(xw);
assert(!QueueEmpty(xw)); // 空队列不能出队,断言保护
if(xw->phead == xw->ptail) // 情况1:队列只有1个节点
{
free(xw->phead); // 释放唯一节点
xw->phead = xw->ptail = NULL; // 头尾置空
}
else // 情况2:队列有多个节点
{
QNode* next = xw->phead->next; // 保存队头下一个节点
free(xw->phead); // 释放队头节点
xw->phead = next; // 更新队头指针
}
xw->size--; // 元素个数-1
}
// 获取队头元素
QDataType QueueFront(Queue* xw)
{
assert(xw);
assert(!QueueEmpty(xw)); // 空队列无队头,断言保护
return xw->phead->val; // 直接返回队头节点的值
}
// 获取队尾元素
QDataType QueueBack(Queue* xw)
{
assert(xw);
assert(!QueueEmpty(xw)); // 空队列无队尾,断言保护
return xw->ptail->val; // 直接返回队尾节点的值
}
// 判断队列是否为空
bool QueueEmpty(Queue* xw)
{
assert(xw);
return xw->size == 0; // size为0则队空
}
// 获取队列元素个数
int QueueSize(Queue* xw)
{
assert(xw);
return xw->size; // 直接返回size,无需遍历,效率高
}
}
test.cpp
cpp
#include"Stack.h"
#include"Queue.h"
int main()
{
// 测试栈功能
gxy::ST st; // 定义栈对象
gxy::STInit(&st, 2); // 初始化栈,初始容量2
gxy::STPush(&st,1); // 入栈:1
gxy::STPush(&st,2); // 入栈:2(此时栈满)
gxy::STPush(&st,3); // 入栈:3(触发扩容)
// 打印栈顶和栈大小,预期:栈顶3,大小3
printf("栈顶:%d 大小:%d\n",gxy::STTop(&st),gxy::STSize(&st));
gxy::STPop(&st); // 出栈:删除3
// 打印栈顶和栈大小,预期:栈顶2,大小2
printf("栈顶:%d 大小:%d\n",gxy::STTop(&st),gxy::STSize(&st));
gxy::STDestroy(&st); // 销毁栈,释放内存
// 测试队列功能
gxy::Queue q; // 定义队列对象
gxy::QueueInit(&q); // 初始化队列
gxy::QueuePush(&q,10); // 入队:10
gxy::QueuePush(&q,20); // 入队:20
gxy::QueuePush(&q,30); // 入队:30
// 打印队头、队尾、大小,预期:队头10,队尾30,大小3
printf("队头:%d 队尾:%d 大小:%d\n",gxy::QueueFront(&q),gxy::QueueBack(&q),gxy::QueueSize(&q));
gxy::QueuePop(&q); // 出队:删除10
// 打印队头、大小,预期:队头20,大小2
printf("队头:%d 大小:%d\n",gxy::QueueFront(&q),gxy::QueueSize(&q));
gxy::QueueDestroy(&q); // 销毁队列,释放所有节点
return 0;
}
2.3 命名空间使用
编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
• 指定命名空间访问,项目中推荐这种方式。
• using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。
• 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。
2.3.1 未使用命名空间的错误示例
cpp
#include<stdio.h>
namespace gxy
{
int a = 0;
int b = 1;
}
int main()
{
// 编译报错: error C2065: "a": 未声明的标识符
printf("%d\n", a);
return 0;
}
报错原因:a 定义在 gxy 命名空间内,属于命名空间作用域,全局作用域找不到a,必须指明命名空间
2.3.2 指定命名空间访问
cpp
#include<stdio.h>
namespace gxy
{
int a = 0;
int b = 1;
}
int main()
{
printf("%d\n", gxy::a); // 指明gxy命名空间,正确访问变量a,输出0
return 0;
}
✅ 核心要点
-
加了gxy:: 精准定位命名空间内的a,解决未声明报错
-
命名空间隔离生效,同时不影响访问
-
运行结果:0
2.3.3 using将命名空间中某个成员展开
cpp
#include<stdio.h>
namespace gxy
{
int a = 0;
int b = 1;
}
// 单独展开gxy命名空间里的b,全局可直接用b
using gxy::b;
int main()
{
printf("%d\n", gxy::a); // a没展开,必须加gxy::
printf("%d\n", b); // b已展开,直接用,输出1
return 0;
}
运行结果:0 1
核心2点:
✅ using gxy::b 只暴露b,不影响a,兼顾便捷与隔离
✅ 仅当前文件有效,不会污染其他文件,比using namespace gxy安全
2.3.4 展开命名空间中全部成员
cpp
#include<stdio.h>
namespace gxy
{
int a = 0;
int b = 1;
}
// 展开gxy命名空间所有成员,全局可直接访问内部所有标识符
using namespace gxy;
int main()
{
// 无需加gxy::,直接访问命名空间内成员,输出0
printf("%d\n", a);
// 直接访问,输出1
printf("%d\n", b);
return 0;
}
💡 关键要点
✅ 效果:一次性暴露gxy所有成员,不用加gxy::,最便捷
✅ 风险:失去命名隔离意义,多模块/多人协作易同名冲突
✅ 场景:仅适合小程序/测试代码,工程开发(如你之前栈队列)严禁用
⚠️ 冲突示例(一用就报错)
cpp
#include<stdio.h>
namespace gxy
{
int a = 0; // 命名空间内的变量a
}
int a = 10; // 全局作用域的变量a
using namespace gxy; // 展开gxy所有成员,触发冲突
int main()
{
// 编译报错:error C2374: "a": 重定义;多次初始化
printf("%d\n", a);
return 0;
}
报错核心原因:
-
using namespace gxy; 会把gxy里的a暴露到全局作用域
-
全局作用域本身已有一个int a=10,两个a作用域重叠、名字相同
-
C++不允许同一作用域存在同名变量,直接报重定义错误
关键细节(易忽略)
✅ 冲突触发顺序:先有2个同名a,再展开命名空间才会冲突;若把using namespace gxy写在int a=10前,报错一致
✅ 本质:展开后,gxy::a和全局a变成「同一作用域的同名变量」,和你之前rand重定义是同一逻辑
✅ 报错信息(VS编译器):error C2374: "a": 重定义;多次初始化
解决办法(2种)
-
删using namespace gxy,用标准写法gxy::a访问
-
全局变量改名,避免和命名空间内成员同名
三、C++输入/输出
核心概念:
<iostream>:Input Output Stream 的缩写,是标准输入输出流库,定义了 std::cin、std::cout 等标准输入输出对象。
std::cin:istream 类的对象,用于窄字符(char 类型)的标准输入。
std::cout:ostream 类的对象,用于窄字符的标准输出。
std::endl:输出时插入换行符并刷新缓冲区。
<<:流插入运算符(输出);>>:流提取运算符(输入)。
优势:自动识别变量类型,无需手动指定格式(如 printf 的 %d/%f)。
命名空间:cout/cin/endl 都在 std 命名空间中,需通过 std:: 或 using namespace std; 使用。
兼容性:包含 <iostream> 后,在 VS 系列编译器中可直接使用 printf/scanf(间接包含了 <stdio.h>),但其他编译器可能报错。
示例代码
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
// 输出方式1:直接使用 cout
cout << a << " " << b << " " << c << endl;
// 输出方式2:显式使用 std::cout
std::cout << a << " " << b << " " << c << std::endl;
// C语言风格输入输出
scanf("%d%lf", &a, &b);
printf("%d %lf\n", a, b);
// C++风格输入
cin >> a;
cin >> b >> c;
// 输出结果
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
性能优化(竞赛常用)
cpp
#include <iostream>
using namespace std;
int main()
{
// 在IO需求高的场景(如竞赛)中,加速C++ IO
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}
四、缺省参数(默认参数)
核心概念:
1) 定义:声明/定义函数时为参数指定默认值。调用时若未传参,使用默认值;否则使用传入的实参。
2) 分类:
全缺省:所有参数都指定默认值。
半缺省:部分参数指定默认值,必须从右往左依次指定,不能间隔跳跃。
3) 调用规则:必须从左到右依次传参,不能跳跃传参。
4) 声明与定义分离:缺省参数只能在函数声明中指定,不能在声明和定义中同时出现。
示例代码
基础用法
cpp
#include <iostream>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 未传参,使用默认值 0 → 输出 0
Func(10); // 传参,使用实参 10 → 输出 10
return 0;
}
全缺省 vs 半缺省
cpp
#include <iostream>
using namespace std;
// 全缺省
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;
}
int main()
{
Func1(); // 全用默认值 → a=10, b=20, c=30
Func1(1); // a=1, b=20, c=30
Func1(1, 2); // a=1, b=2, c=30
Func1(1, 2, 3); // a=1, b=2, c=3
Func2(100); // a=100, b=10, c=20
Func2(100, 200); // a=100, b=200, c=20
Func2(100, 200, 300); // a=100, b=200, c=300
return 0;
}
声明与定义分离
- 头文件 Stack.h
cpp
#include <iostream>
#include <cassert>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a; // 栈的底层数组
int top; // 栈顶指针(指向栈顶元素的下一个位置)
int capacity; // 栈的容量
} ST;
// 声明时指定缺省参数,默认初始化容量为4
void STInit(ST* ps, int n = 4);
• STDataType 是类型别名,方便后续修改栈存储的数据类型。
• struct Stack 定义了栈的结构,包含动态数组、栈顶指针和容量。
• STInit 函数声明时给 n 设了默认值 4,这样调用时可以不传参数。
- 实现文件 Stack.cpp
cpp
#include "Stack.h"
#include <cstdlib>
// 定义时不能再指定缺省参数
void STInit(ST* ps, int n)
{
assert(ps && n > 0); // 检查指针有效性和容量合法性
ps->a = (STDataType*)malloc(n * sizeof(STDataType)); // 动态分配内存
ps->top = 0; // 栈顶初始化为0(空栈状态)
ps->capacity = n; // 记录栈的容量
}
• assert(ps && n > 0):确保传入的栈指针不为空,且初始化容量大于0,避免后续操作出错。
• malloc 为栈分配 n 个 STDataType 大小的内存空间。
• ps->top = 0 表示栈为空,新元素会从索引0开始存放。
- 测试文件 test.cpp
cpp
#include "Stack.h"
int main()
{
ST s1;
STInit(&s1); // 未传参,使用默认值4 → 初始化容量为4的栈
ST s2;
STInit(&s2, 1000); // 传入实参1000 → 初始化容量为1000的栈,避免后续扩容
return 0;
}
• STInit(&s1):使用默认参数,适合不确定初始容量的场景。
• STInit(&s2, 1000):显式指定容量,适合已知数据量较大的场景,减少后续扩容的开销。
五、 函数重载
核心概念
• 定义:同一作用域中允许出现同名函数,但形参列表必须不同(参数个数、类型、顺序不同均可)。
• C语言不支持:C语言通过函数名直接链接,同名函数会导致链接错误。
• 注意:返回值不同不能作为重载的依据(调用时无法区分)。
示例代码
cpp
#include <iostream> // 引入输入输出流头文件,用于cout输出
using namespace std; // 使用标准命名空间,可直接用cout无需写std::cout
// 1. 参数类型不同构成函数重载:int类型加法
int Add(int left, int right) // 两个int类型形参
{
cout << "int Add(int left, int right)" << endl; // 打印当前调用的函数版本
return left + right; // 返回int类型两数之和
}
// 与上面Add构成重载:参数类型为double
double Add(double left, double right) // 两个double类型形参
{
cout << "double Add(double left, double right)" << endl; // 打印当前调用的函数版本
return left + right; // 返回double类型两数之和
}
// 2. 参数个数不同构成函数重载:无参版本f
void f() // 无参数
{
cout << "f()" << endl; // 打印当前调用的函数版本
}
// 与上面f构成重载:带1个int参数
void f(int a) // 1个int类型形参
{
cout << "f(int a)" << endl; // 打印当前调用的函数版本
}
// 3. 参数类型顺序不同构成函数重载:先int后char
void f(int a, char b) // 第1个int,第2个char形参
{
cout << "f(int a, char b)" << endl; // 打印当前调用的函数版本
}
// 与上面f构成重载:先char后int
void f(char b, int a) // 第1个char,第2个int形参
{
cout << "f(char b, int a)" << endl; // 打印当前调用的函数版本
}
// 错误示例:仅返回值不同不能作为函数重载的条件,编译器会报重复定义错误
// void fxx() {}
// int fxx() { return 0; }
// 错误示例:重载函数搭配缺省参数,会导致调用时歧义,编译器无法区分调用哪个版本
// void f1() { cout << "f1()" << endl; }
// void f1(int a = 10) { cout << "f1(int a)" << endl; }
int main() // 程序入口函数
{
Add(10, 20); // 实参都是int,调用int版本Add
Add(10.1, 20.2); // 实参都是double,调用double版本Add
f(); // 无实参,调用无参版本f
f(10); // 1个int实参,调用单int参数版本f
f(10, 'a'); // 实参int+char,调用(int,char)版本f
f('a', 10); // 实参char+int,调用(char,int)版本f
return 0; // 程序正常结束,返回0
}