C++入门基础

文章目录

C++核心特性详解(基础增强版)


一、第一个C++程序:Hello World

完整代码解析

cpp 复制代码
// 包含标准输入输出流头文件
#include <iostream>

// 主函数:程序入口
int main() 
{
    // std::cout 标准输出流对象
    // << 流插入运算符(将右侧内容输出到左侧流)
    // std::endl 换行并刷新缓冲区
    std::cout << "Hello World!" << std::endl;
    
    // 返回0表示程序正常退出
    return 0;
}

新手常见问题

  1. 为什么用std::cout不用printf

    • 类型安全:自动识别变量类型
    • 扩展性好:支持自定义类型输出
    • 代码可读性:<<运算符链式调用更直观
  2. std::endl\n的区别?

    • \n:仅换行
    • std::endl:换行+立即刷新输出缓冲区

二、命名空间(详解版)

1. 为什么需要命名空间?

经典冲突案例

c 复制代码
// math.h
int abs(int val) { return val>0?val:-val; }

// main.cpp
#include <math.h>
#include <stdlib.h> // 包含标准库的abs()

int main() {
    printf("%d", abs(-5)); // 编译错误:函数重定义
    return 0;
}

2. 命名空间使用场景

cpp 复制代码
namespace MySpace {
    int version = 1;
    void Print() { /*...*/ }
    class Data { /*...*/ };
}

// 使用场景1:明确指定
MySpace::Print();

// 使用场景2:局部展开
using MySpace::version;
cout << version;

// 使用场景3:全局展开(慎用!)
using namespace MySpace;

3. 嵌套命名空间

cpp 复制代码
namespace Parent {
    int x = 10;
    namespace Child {
        int y = 20;
    }
}

// 访问方式
cout << Parent::Child::y; // 输出20

4. 匿名命名空间

cpp 复制代码
namespace {
    int count = 0; // 仅当前文件可见
}

三、输入输出系统(深度解析)

1. 输入输出对比

特性 C (printf/scanf) C++ (cin/cout)
类型安全 需要格式说明符 自动类型推导
扩展性 不支持自定义类型 支持运算符重载扩展
错误处理 无编译时检查 流状态检测
函数参数 可变参数列表 运算符链式调用

2. 格式化输出

cpp 复制代码
#include <iomanip> // 需要包含此头文件

cout << fixed << setprecision(2) << 3.14159; // 输出3.14
cout << hex << 255;                          // 输出ff

3. 输入注意事项

cpp 复制代码
int age;
double salary;

// 正确写法:连续输入
cin >> age >> salary;

// 错误处理示例
if (!(cin >> age)) {
    cout << "输入错误!";
    cin.clear(); // 清除错误状态
    cin.ignore(1024, '\n'); // 清空缓冲区
}

四、函数重载(原理剖析)

1. 重载条件深度解析

cpp 复制代码
// 合法重载
void func(int a) {}        // 类型不同
void func(double a) {}

void func(int a, int b) {} // 参数个数不同

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

// 非法重载
int func(int a) {}         // 仅返回类型不同 ❌
void func(const int a) {}  // const修饰值参数 ❌

2. 底层实现原理

编译器通过**名称修饰(Name Mangling)**生成唯一符号:

  • void func(int)_Z4funci
  • void func(double)_Z4funcPd

查看方法(Linux):

bash 复制代码
g++ -c test.cpp
nm test.o

五、缺省参数(陷阱解析)

1. 使用规范示例

cpp 复制代码
// 正确:全缺省
void Connect(string ip = "127.0.0.1", int port = 3306) {}

// 正确:半缺省(必须从右往左)
void Print(int a, int b = 10, int c = 20) {}

// 错误示例
void Error1(int a = 10, int b) {} // 非连续缺省 ❌
void Error2(int a, int b = 10, int c) {} // 中间参数缺省 ❌

2. 声明定义分离规范

cpp 复制代码
// test.h
void Init(int timeout = 1000); // 声明处给缺省值

// test.cpp
void Init(int timeout) { /*...*/ } // 定义处不能重复给

3. 常见陷阱

cpp 复制代码
void func(int a, int b = 10) {}
func(,20); // 错误!必须从左向右传参 ❌

六、引用机制(深入理解)

1. 引用本质

cpp 复制代码
int a = 10;
int& ra = a; 

// 底层实现等效于:
int* const ra = &a;  // 常指针(指向不可变)
*ra = 20;            // 解引用操作

2. 引用使用场景

场景1:函数参数传递

cpp 复制代码
void Swap(int& x, int& y) {
    int tmp = x;
    x = y;
    y = tmp;
}

场景2:函数返回值

cpp 复制代码
int& GetElement(int* arr, int index) {
    return arr[index];
}

3. 权限问题详解

cpp 复制代码
// 示例1:权限放大
const int a = 10;
int& ra = a;  // 错误!ra可能修改a ❌

// 示例2:权限缩小
int b = 20;
const int& rb = b; // 合法 ✅

// 示例3:临时对象
int c = 10;
double& rd = c;     // 错误!类型转换产生临时对象 ❌
const double& rd = c; // 正确 ✅

七、内联函数(实现原理)

1. 编译器处理逻辑

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

int main() {
    int ret = Add(1,2);
    // 可能被展开为:
    // int ret = 1 + 2;
    return 0;
}

2. 查看展开效果(GCC)

bash 复制代码
g++ -S test.cpp -o test.s

汇编文件内容:

assembly 复制代码
main:
    movl    $3, -4(%rbp)  // 直接计算结果

3. 使用限制

情况 是否支持内联
递归函数
函数体超过10行 由编译器决定
虚函数

八、符号表与链接错误

1. 多文件编译流程

.cpp文件 → 编译器 → 目标文件(.o) → 链接器 → 可执行文件
                (符号表)          (符号解析)

2. 典型错误分析

错误现象:

重复定义`void Print()`

错误原因:

cpp 复制代码
// utils.h
void Print() { /*...*/ } // 被多个cpp文件包含导致多份定义

解决方案:

cpp 复制代码
// utils.h
inline void Print() { /*...*/ }  // 方案1:使用inline

// 或
// utils.h
void Print(); // 声明

// utils.cpp
void Print() { /*...*/ } // 定义

九、nullptr(深度解析)

1. NULL的局限性

cpp 复制代码
void func(int) {}
void func(int*) {}

func(NULL);     // 调用func(int) ❌
func(nullptr);  // 正确调用func(int*) ✅

2. nullptr特性

cpp 复制代码
// 类型检测
cout << typeid(nullptr).name();  // 输出Dn (表示decltype(nullptr))
cout << typeid(NULL).name();     // 输出l (表示long)

// 安全转换
int* p1 = nullptr;     // ✅
int* p2 = NULL;        // ✅(C++11前可能警告)
int num = nullptr;     // ❌ 类型不匹配

十、最佳实践指南

1. 命名空间使用建议

  • 项目开发:始终使用命名空间::成员形式
  • 小型测试:可using namespace 命名空间
  • 禁止:在头文件中使用全局using指令

2. 函数设计原则

  • 优先使用引用传参(避免拷贝)
  • 参数超过3个考虑结构体封装
  • 超过50行代码的函数避免使用inline

3. 指针/引用选择

场景 推荐使用
需要指向不同对象 指针
函数参数传递 const引用
返回值需要空值 指针
实现多态 指针/引用

补充说明

各种报错分析

1.全局函数(声明和定义在一块)被多个.c/.cpp文件展开,报错类型重定义函数,因为这样会导致函数多个进入符号表(粗暴认为符号表内存有的是函数名及其函数地址)内导致重定义

2.解决方法:1.定义和声明分离,由于分.h文件和.c/.cpp文件此时不会进入符号表,之后进行链接操作根据函数名及其地址找到.c/.cpp文件中的定义链接一块即可 2.将函数处理为static放入代码段(常量区),这样就不会进入符号表就算多个.h文件包含也不会因为在符号表内重定义

2.内联函数定义和声明分离和未分离相关讨论

分离:这时候由于内联函数也不会进入符号表因为内联函数使用时是要展开的,这样就没必要进入符号表存入函数名和地址了,但是此时.c/,cpp文件调用这个函数链接时内联展开只有函数声明没有定义这时候导致链接错误

未分离:这样不会出现以上描述情况因为声明和定义未分离可以正常链接

结语

通过这份增强版教程,我们系统性地梳理了C++核心特性,从最基础的Hello World程序到复杂的引用机制,每个知识点都配有详细的代码示例和原理剖析。建议学习时:

  1. 按照章节顺序逐步实践
  2. 重点理解各特性的设计初衷
  3. 通过修改示例代码观察不同表现
  4. 遇到问题优先查看编译器错误提示

C++的学习曲线虽然陡峭,但掌握这些基础后,后续面向对象、模板等高级特性将会更加得心应手。保持编码实践,持续积累经验!

相关推荐
suxiaoling@3 小时前
C# 使用GDI+设计登录窗体(窗体渐变和关闭淡出)
开发语言·c#
邦之彦4 小时前
C++:四大强制类型转换
开发语言·c++·static_cast·const_cast·dynamic_cast·reinterpretcast
陆鳐LuLu5 小时前
初学者如何用 Python 写第一个爬虫?
开发语言·爬虫·python
chuhx5 小时前
java 查找两个集合的交集部分数据
java·开发语言
AKAGSBGM6 小时前
PHP动态网站建设
开发语言·php
落落落sss6 小时前
分布式日志和责任链路
java·运维·开发语言·后端·jenkins
C_V_Better6 小时前
Java 导出 PDF 文件:从入门到实战
java·开发语言·算法·pdf
ks不知火7 小时前
【仿muduo库one thread one loop式并发服务器实现】
linux·c++
mit6.8247 小时前
[Lc(2)滑动窗口_1] 长度最小的数组 | 无重复字符的最长子串 | 最大连续1的个数 III | 将 x 减到 0 的最小操作数
数据结构·c++·算法·leetcode