C++第一课

一、 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个关键知识点)

  1. 头文件引入影响:stdlib.h 是C标准库头文件,引入后会暴露rand()函数(声明:int rand(void);),作用域覆盖整个文件

  2. 命名冲突规则:C语言中,变量名和函数名属于同一命名空间,不能重复定义/声明

  3. 全局变量特性:你定义的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;
}

细节拆解如下:

  1. 核心原理:namespace(命名空间)是C++特性,用来隔离命名,避免全局命名冲突,gxy空间里的rand和stdlib.h的rand函数互不干扰

  2. 访问规则:不加命名空间默认访问全局(这里是stdlib.h的rand函数,打印出函数地址);加gxy:: 才访问自定义命名空间里的int型rand变量

  3. 兼容性提醒:你的代码后缀如果是.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点解析:

  1. 嵌套逻辑:gxy是父命名空间,xw/xz是子命名空间,层级访问用::串联,互不干扰

  2. 冲突隔离:xw和xz里的rand、Add同名完全不冲突,精准访问对应域的成员

  3. 编译要求:后缀必须是.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;
}
  1. 本质原理(关键)

同一个工程里,所有同名namespace(比如你所有文件里的gxy),会被编译器视为同一个命名空间,里面的成员(结构体/函数/变量)会合并到一起,不会冲突。

  1. 代码实战对应(一看就懂)

多文件同名gxy,就是标准用法:

• Stack.h/Stack.cpp → 把栈相关的ST、STPush等放进gxy

• Queue.h/Queue.cpp → 把队列相关的Queue、QueuePush等放进gxy

• 编译器最终合并成1个gxy,里面同时有栈+队列的所有成员,访问都用gxy::xxx

  1. 核心规则(避坑必记)

合并规则:同名namespace不管在多少文件,成员都合并,成员名不能重复(比如不能在两个文件的gxy里都定义int rand)

声明与实现分离:头文件(.h)放namespace里的声明(结构体、函数声明),源文件(.cpp)放同名namespace里的实现,必须一致

头文件防护:必须加#pragma once,防止头文件重复包含导致成员重复声明报错

  1. 实用好处(为什么要这么用)

拆分代码:把栈、队列等不同模块拆到多文件,代码整洁,维护方便

全局隔离:所有模块共用一个命名空间,对外只暴露一个gxy::,避免和其他代码冲突

扩展方便:后续加链表、树等模块,直接放进同名namespace,无需改原有代码

  1. 注意坑点

• 同名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;
}

✅ 核心要点

  1. 加了gxy:: 精准定位命名空间内的a,解决未声明报错

  2. 命名空间隔离生效,同时不影响访问

  3. 运行结果: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;
}

报错核心原因:

  1. using namespace gxy; 会把gxy里的a暴露到全局作用域

  2. 全局作用域本身已有一个int a=10,两个a作用域重叠、名字相同

  3. C++不允许同一作用域存在同名变量,直接报重定义错误

关键细节(易忽略)

✅ 冲突触发顺序:先有2个同名a,再展开命名空间才会冲突;若把using namespace gxy写在int a=10前,报错一致

✅ 本质:展开后,gxy::a和全局a变成「同一作用域的同名变量」,和你之前rand重定义是同一逻辑

✅ 报错信息(VS编译器):error C2374: "a": 重定义;多次初始化

解决办法(2种)

  1. 删using namespace gxy,用标准写法gxy::a访问

  2. 全局变量改名,避免和命名空间内成员同名

三、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;
}

声明与定义分离

  1. 头文件 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,这样调用时可以不传参数。

  1. 实现文件 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开始存放。

  1. 测试文件 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
}
相关推荐
changyunkeji2 小时前
电缆输送机,高质量产品助力行业快速发展
经验分享·科技
Coding茶水间2 小时前
基于深度学习的路面裂缝检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
开发语言·人工智能·深度学习·yolo·目标检测·机器学习
蓝田生玉1232 小时前
PLUTO论文阅读笔记
论文阅读·笔记
charlie1145141912 小时前
现代嵌入式C++教程:对象池(Object Pool)模式
开发语言·c++·学习·算法·嵌入式·现代c++·工程实践
小丁努力不焦虑2 小时前
你在以前遇到了什么困难你又是如何解决的?
学习
HABuo2 小时前
【linux进程控制(三)】进程程序替换&自己实现一个bash解释器
linux·服务器·c语言·c++·ubuntu·centos·bash
我命由我123452 小时前
Android Studio - Android Studio 中的 View Live Telemetry
经验分享·学习·android studio·学习方法·android jetpack·android-studio·android runtime
TTGGGFF2 小时前
控制系统建模仿真(二):掌握控制系统设计的 MAD 流程与 MATLAB 基础运算
开发语言·数据结构·matlab
xiaoxiaoxiaolll2 小时前
面向集成微系统供电:《Light》揭示石墨烯混合材料微型电容器的结构化电极设计与性能优化
学习