🧠 C++ 四级核心概念 —— 引用·字符串·指针·作用域·文件·extern/static·形参实参·异或

哈喽同学们!今天我们课上攻克了 C++ 里最容易混淆的九个核心概念~这些都是选择题和代码阅读题的高频考点,搞懂了它们,C++ 基础就真正扎实了!准备好了吗?开始!🚀


📚 今日知识地图

序号 知识点 核心问题
1️⃣ & 的双重身份 取地址 vs 引用,别搞混!
2️⃣ 字符串常量 vs 字符数组 一个不能改,一个能改
3️⃣ cout 输出字符指针 输出的是字符串不是地址!
4️⃣ 全局变量 vs 局部变量 生命周期和作用域大不同
5️⃣ ifstream / ofstream 文件读写基础
6️⃣ 指针数组 vs 数组指针 名字像,含义天差地别
7️⃣ extern / static 跨文件引用与静态存储
8️⃣ 形参与实参 值传递的秘密
9️⃣ ^ 是异或不是幂 别把运算符用错了!

1️⃣ & 的双重身份:取地址 & 引用

💡 引入:一个符号,两种含义

& 这个符号在 C++ 里有两个完全不同的身份,很多同学搞混:

用法 含义 例子
&a(放在变量前面) 取地址:取出变量 a 在内存中的地址 int *p = &a;
int &b = a(放在类型后面) 声明引用:b 是 a 的别名(别名就是另一个名字) int &b = a;

🎯 引用是什么?—— 给变量起个"外号"

引用就是给一个已有的变量起一个别名。通过别名操作变量,和直接操作原变量完全一样,因为它们指向同一块内存

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int &b = a;   // b 是 a 的引用(别名)

    cout << "a = " << a << endl;   // 10
    cout << "b = " << b << endl;   // 10

    b = 20;        // 通过别名修改
    cout << "a = " << a << endl;   // 20!a 也变了
    cout << "b = " << b << endl;   // 20

    cout << "a 的地址:" << &a << endl;
    cout << "b 的地址:" << &b << endl;  // 和 a 完全一样!

    return 0;
}

💡 敲黑板:引用必须在声明时就初始化,而且一旦绑定就不能再改成别的变量的别名!

int a = 1, c = 2;
int &b = a;   // ✅ 正确,声明时初始化
int &r;       // ❌ 错误!引用必须初始化
b = c;        // ⚠️ 这不是改绑定,而是把 c 的值赋给 b(也就是赋给 a)

🎯 取地址是什么?—— 找到变量在内存中的"门牌号"

每个变量在内存中都有一个地址,&a 就能取出这个地址,通常配合指针使用:

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int *p = &a;   // p 是指针,存的是 a 的地址

    cout << "a 的值:" << a << endl;     // 10
    cout << "a 的地址:" << &a << endl;  // 类似 0x61ff0c
    cout << "p 的值:" << p << endl;     // 和 &a 一样
    cout << "*p 的值:" << *p << endl;   // 10(解引用,取出地址里存的值)

    return 0;
}

🚨 易错点

❌ 错误做法 ✅ 正确做法
int &b; 只声明不初始化 引用必须在声明时绑定变量
以为引用能重新绑定别的变量 引用一旦绑定不可更改,b=c 只是赋值
&aint &b 搞混 看位置:变量前是取地址,类型后是引用声明
以为引用和指针一样 引用是别名(地址相同),指针是变量(存地址,自身也有地址)

💡 口诀:& 有两个面,变量前面取地址,类型后面是引用;引用就是起外号,地址相同跑不掉,声明必须就初始化,绑定之后改不了!


2️⃣ 字符串常量 vs 字符数组

💡 引入:同样是字符串,待遇大不同

看这两行代码:

char *p = "hello";     // 字符串常量
char a[] = "hello";    // 字符数组

看起来都表示 "hello",但它们在内存中的位置和可修改性完全不同!


🎯 字符串常量:住在"只读区",不能改

char *p = "hello";
p[0] = 'H';   // ❌ 运行时崩溃!字符串常量存在只读数据区,不能修改

字符串常量 "hello" 被编译器放在只读数据段,程序运行时不能修改它。试图修改会导致未定义行为(通常直接崩溃)。

💡 小提示:C++ 里更规范的写法是 const char *p = "hello";,加上 const 让编译器帮你拦住误修改!


🎯 字符数组:住在"栈上",随便改

char a[] = "hello";
a[0] = 'H';   // ✅ 没问题!数组在栈上,可以修改
cout << a;    // 输出 Hello

字符数组 char a[] = "hello" 会在栈上分配 6 个字节(5个字符 + 1个结束符 \0),把字符串内容拷贝进去,所以可以随便改。


🎯 完整对比示例

#include <iostream>
using namespace std;

int main() {
    // 字符串常量(只读)
    const char *p = "hello";
    cout << p << endl;        // hello
    // p[0] = 'H';           // ❌ 不能改!

    // 字符数组(可改)
    char a[] = "hello";
    a[0] = 'H';              // ✅ 可以改
    cout << a << endl;        // Hello

    // 大小对比
    cout << "数组 a 的大小:" << sizeof(a) << endl;  // 6(5个字符+\0)
    cout << "指针 p 的大小:" << sizeof(p) << endl;  // 4或8(指针本身大小)

    return 0;
}

🚨 易错点

❌ 错误做法 ✅ 正确做法
修改字符串常量 char *p="abc"; p[0]='A'; 要修改就用字符数组 char a[]="abc";
strlen 求数组大小 strlen 求的是字符个数(不含\0),sizeof 才是数组总字节数
字符数组忘记留 \0 的位置 char a[5]="hello"; ❌ 越界,要 char a[6]="hello";
以为 char *pchar a[] 完全一样 p 是指针(4/8字节),a 是数组(整个字符串大小)

💡 口诀:字符串常量只读区,想改它呀没门去;字符数组栈上住,增删改查都自如;常量前面加 const,编译器来帮你护!


3️⃣ cout 输出字符指针:输出的是字符串,不是地址!

💡 引入:一个反直觉的特性

看这段代码,你觉得输出什么?

char a[] = "hello";
char *p = a;
cout << p << endl;   // 输出什么?

很多同学以为会输出一个地址(类似 0x61ff0c),但实际上输出的是:

hello

为什么?因为 coutchar* 类型做了特殊处理:它不会输出指针的值(地址),而是把这个地址当作字符串的起始位置,从这个位置开始逐个输出字符,直到遇到结束符 \0 为止!


🎯 完整示例

#include <iostream>
using namespace std;

int main() {
    char a[] = "hello";
    char *p = a;

    cout << p << endl;        // hello(从a[0]开始输出到\0)
    cout << p + 1 << endl;    // ello(从a[1]开始输出)
    cout << p + 3 << endl;    // lo(从a[3]开始输出)

    // 那怎么输出地址呢?强转为 void*!
    cout << (void *)p << endl;   // 0x61ff0c(真正的地址)

    // 对比:int 指针输出的就是地址
    int x = 10;
    int *q = &x;
    cout << q << endl;        // 0x61ff08(int* 没有特殊处理,输出地址)

    return 0;
}

💡 敲黑板:只有 char*(和 const char*unsigned char* 等字符指针)有这个特殊待遇!其他类型的指针(如 int*double*)用 cout 输出的就是地址本身。


🎯 经典考题:指针数组输出

char *s[] = {(char *)"2024", (char *)"3.16", (char *)"GESP"};
for (int i = 0; i < 2; ++i)
    cout << *s + i << "\n";

手动分析

  • s 是指针数组,s[0] 指向 "2024"s[1] 指向 "3.16"
  • *s 等价于 s[0],即指向 "2024" 的字符指针
  • *s + 0:从 "2024" 第 0 位开始输出 → 2024
  • *s + 1:从 "2024" 第 1 位开始输出 → 024

输出结果

2024
024

💡 小提示:这里 *s + i 不是 *(s+i)*s 先算(得到 s[0]),再加 i。如果是 *(s+i) 那就是 s[i] 了,输出会完全不同。运算符优先级要分清!


🚨 易错点

❌ 错误做法 ✅ 正确做法
以为 cout << char* 输出地址 char* 输出的是字符串,要输出地址强转 (void*)
*s + i*(s+i) 搞混 前者是 s[0]+i(偏移),后者是 s[i](取元素)
输出字符串时没有 \0 结束符 字符串必须以 \0 结尾,否则会一直输出到内存中遇到0为止
以为所有指针输出都是字符串 只有 char* 特殊,int/double 输出地址

💡 口诀:字符指针遇 cout,不输出地址输出串;从头走到结束符,一个字符不落空;想看地址怎么办,void 强转就实现!


4️⃣ 全局变量 vs 局部变量:生命周期与作用域

💡 引入:变量也有"寿命"和"活动范围"

C++ 里的变量根据定义位置不同,分为全局变量局部变量,它们的寿命和活动范围完全不同!


🎯 核心概念对比

全局变量 局部变量
定义位置 所有函数外面 函数内部或代码块 {} 内部
生命周期 整个程序运行期间都存在 进入代码块时创建,离开时销毁
作用域 整个程序都能访问 只在定义它的代码块内有效
默认初始值 自动初始化为 0 未初始化时是随机值(垃圾值)

🎯 经典坑题:for 循环里的 i 出了循环还能用吗?

看这段代码:

int x = 0;
for (int i = 0; i < 9; ++i)
    ++x;
if (i == 9)      // ❌ 编译错误!i 未定义
    cout << x;

为什么报错?

因为 for (int i = 0; ...) 里定义的 i,它的作用域仅限于 for 循环!即使 for 后面没有大括号(只有一条语句 ++x;),i 也属于 for 循环的代码块,循环一结束,i 就被回收了。

💡 敲黑板:C++ 标准规定,在 for 循环的初始化语句中定义的变量,作用域是整个 for 循环(包括循环体),循环结束后变量就不存在了。

正确写法:把 i 定义在 for 外面:

int x = 0;
int i;                    // 定义在外面
for (i = 0; i < 9; ++i)
    ++x;
if (i == 9)               // ✅ 现在 i 还在
    cout << x;             // 输出 9

🎯 完整示例:全局 vs 局部

#include <iostream>
using namespace std;

int g = 100;   // 全局变量,整个程序都能用,自动初始化为0(这里显式给了100)

void func() {
    cout << "func 里访问全局 g = " << g << endl;  // ✅ 可以访问全局变量
    int a = 10;   // 局部变量,只在 func 里有效
    cout << "func 里的局部 a = " << a << endl;
}

int main() {
    cout << "main 里访问全局 g = " << g << endl;  // 100
    // cout << a;   // ❌ 错误!a 是 func 的局部变量,main 里访问不到

    func();

    int x = 0;
    for (int i = 0; i < 3; ++i) {
        x += i;
        cout << "循环内 i = " << i << endl;  // ✅ i 在循环内有效
    }
    // cout << i;   // ❌ 错误!i 已经被回收

    g = 200;       // ✅ 修改全局变量
    cout << "修改后 g = " << g << endl;  // 200

    return 0;
}

🚨 易错点

❌ 错误做法 ✅ 正确做法
for 循环外使用 for(int i=...) 里的 i 需要循环后用 i,就把 i 定义在 for 外面
局部变量不初始化就使用 局部变量默认是垃圾值,使用前必须初始化
在函数里修改局部变量想影响外面 局部变量函数结束就销毁,不会影响外面(除非用引用/指针)
全局变量和局部变量同名 局部会屏蔽全局(就近原则),尽量避免同名造成混淆

💡 口诀:全局变量活全程,函数外面来定义;局部变量活块内,块一结束就销毁;for 里定义的 i,出了循环就没戏;局部记得初始化,垃圾值呀害死人!


5️⃣ ifstream / ofstream:文件读写基础

💡 引入:让程序和文件"对话"

有时候我们需要从文件里读数据,或者把结果写到文件里。C++ 提供了两个文件流类:

类名 头文件 作用
ifstream <fstream> input file stream,从文件数据
ofstream <fstream> output file stream,向文件数据

用法和 cin/cout 几乎一样,只是数据源从键盘/屏幕变成了文件!


🎯 写文件:ofstream

#include <iostream>
#include <fstream>   // 必须包含这个头文件
using namespace std;

int main() {
    ofstream fout("output.txt");   // 创建/打开 output.txt 用于写入

    fout << "Hello, File!" << endl;  // 像 cout 一样用
    fout << "1 + 2 = " << 1 + 2 << endl;

    int a[] = {10, 20, 30};
    for (int i = 0; i < 3; ++i)
        fout << a[i] << " ";

    fout.close();   // 用完关闭文件(好习惯)
    return 0;
}

运行后 output.txt 内容:

Hello, File!
1 + 2 = 3
10 20 30 

🎯 读文件:ifstream

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream fin("input.txt");   // 打开 input.txt 用于读取

    if (!fin) {                  // 判断文件是否打开成功
        cout << "文件打开失败!" << endl;
        return 0;
    }

    int x, y;
    fin >> x >> y;               // 像 cin 一样读
    cout << "读到:x = " << x << ", y = " << y << endl;

    string s;
    while (fin >> s)             // 一直读到文件结束
        cout << "读到单词:" << s << endl;

    fin.close();
    return 0;
}

假设 input.txt 内容:

3 5
apple banana cherry

输出:

读到:x = 3, y = 5
读到单词:apple
读到单词:banana
读到单词:cherry

🎯 读写结合:复制文件

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream fin("source.txt");
    ofstream fout("target.txt");

    char c;
    while (fin.get(c))    // 逐个字符读
        fout.put(c);      // 逐个字符写

    fin.close();
    fout.close();
    cout << "复制完成!" << endl;
    return 0;
}

🚨 易错点

❌ 错误做法 ✅ 正确做法
忘记 #include <fstream> 文件操作必须包含 fstream 头文件
不判断文件是否打开成功 if(!fin)if(fin.fail()) 判断
用完不关闭文件 调用 .close() 关闭,防止数据丢失
ofstream 打开已存在文件想追加内容 默认会覆盖!要追加用 ofstream fout("a.txt", ios::app);
文件名路径写错 相对路径是相对于程序运行目录,注意路径分隔符用 /\\

💡 口诀:ifstream 读文件,ofstream 写文件,用法就像 cin/cout;头文件是 fstream,打开失败要判断,用完 close 好习惯,追加模式用 ios::app!


6️⃣ 指针数组 vs 数组指针:名字像,含义天差地别

💡 引入:两个极易混淆的概念

char *s[10];    // 指针数组
char (*p)[10];  // 数组指针

就差一个括号,含义完全不同!这是 C++ 里最经典的易混点之一,选择题必考!


🎯 指针数组:是数组,每个元素是指针

char *s[3];   // 数组,有3个元素,每个元素是 char* 指针

读法:先看 s[3](s 是数组),再看 char *(数组元素是 char 指针)。所以叫"指针的数组"→ 指针数组。

内存布局:s 是一个数组,占 3 个指针的大小(3×4=12 字节或 3×8=24 字节),每个元素可以指向一个字符串。

char *s[] = {(char *)"2024", (char *)"3.16", (char *)"GESP"};
// s[0] → "2024"
// s[1] → "3.16"
// s[2] → "GESP"

🎯 数组指针:是指针,指向一个数组

char (*p)[10];   // 指针,指向一个有10个char元素的数组

读法:先看 (*p)(p 是指针),再看 char [10](指向的是 10 个 char 的数组)。所以叫"指向数组的指针"→ 数组指针。

内存布局:p 就是一个指针(4 或 8 字节),它指向一个长度为 10 的 char 数组。

char a[3][10] = {"hello", "world", "cpp"};
char (*p)[10] = a;   // p 指向 a 的第一行(一个含10个char的数组)

cout << *p << endl;      // hello(p指向第一行,*p是第一行的首地址)
cout << *(p + 1) << endl; // world(p+1跳到下一行)

💡 小提示:数组指针常用于操作二维数组!p+i 跳到第 i 行,*(p+i)+j 是第 i 行第 j 列的地址,*(*(p+i)+j) 就是 a[i][j]


🎯 核心对比表

指针数组 char *s[N] 数组指针 char (*p)[N]
本质 数组 指针
读法 s 是数组,元素是 char* p 是指针,指向 char[N] 数组
大小 N × 指针大小 指针大小(4/8字节)
常见用途 存多个字符串(如命令行参数) 操作二维数组
s+1 跳到下一个指针元素 跳到下一行数组(跳 N 个 char)

🎯 记忆口诀:括号定乾坤

[] 的优先级比 \* 高!

  • char *s[N]:s 先和 [N] 结合 → s 是数组 → 指针数组
  • char (*p)[N]:括号让 p 先和 * 结合 → p 是指针 → 数组指针

括号决定了谁先结合,也就决定了本质是数组还是指针!


🚨 易错点

❌ 错误做法 ✅ 正确做法
char *p[N] 当成指针用 它是数组,sizeof 是 N×指针大小
char (*p)[N] 当成数组用 它是指针,sizeof 是指针大小
二维数组传参用 char a[][N] 却写成 char **a 二维数组传参退化的是数组指针 char (*)[N],不是二级指针
指针数组元素未初始化就使用 每个指针要先指向有效内存再用

💡 口诀:指针数组是数组,每个元素是指针;数组指针是指针,指向整个数组行;方括号优先级比星高,括号一换天地摇!


7️⃣ extern 和 static 的用法

💡 引入:跨文件和静态存储

当程序变大,分成多个 .cpp 文件时,就需要 externstatic 来管理变量和函数的可见性。


🎯 extern:声明"这个变量/函数在别的文件里定义了"

extern 用来声明一个在其他文件中定义的全局变量或函数,让当前文件也能使用它。

文件 a.cpp:

int count = 0;   // 定义全局变量

void add() {
    count++;
}

文件 b.cpp:

#include <iostream>
using namespace std;

extern int count;   // 声明:count 在别的文件定义了,这里可以用
extern void add();  // 声明函数(函数的 extern 可省略,写不写都行)

int main() {
    add();
    add();
    cout << count << endl;   // 输出 2
    return 0;
}

💡 敲黑板extern 只是声明,不是定义!它告诉编译器"这个变量在别处有定义",不会分配内存。定义(带初始化或不带 extern 的声明)才分配内存。


🎯 static:三种用法,个个重要

用法 1:修饰全局变量/函数 → 只在本文件可见(内部链接)

// file1.cpp
static int secret = 42;   // 静态全局变量,只在 file1.cpp 里能用

static void helper() {     // 静态函数,只在 file1.cpp 里能用
    cout << "helper" << endl;
}
// file2.cpp
extern int secret;   // ❌ 链接错误!secret 是 static 的,别的文件看不到

💡 用途:防止不同文件里的同名全局变量/函数冲突。加了 static 就只在本文件有效,相当于"私有化"了。

用法 2:修饰局部变量 → 静态存储,函数结束不销毁

#include <iostream>
using namespace std;

void func() {
    static int cnt = 0;   // 静态局部变量,只初始化一次,函数结束不销毁
    cnt++;
    cout << "第 " << cnt << " 次调用" << endl;
}

int main() {
    func();   // 第 1 次调用
    func();   // 第 2 次调用
    func();   // 第 3 次调用
    return 0;
}

输出

第 1 次调用
第 2 次调用
第 3 次调用

💡 关键点:静态局部变量 static int cnt = 0; 只在第一次调用函数时初始化一次,之后调用保留上次的值。它存在全局数据区,不是栈上,所以函数结束不会销毁。

用法 3:修饰类的成员(面向对象阶段会学)

static 还能修饰类的成员变量和成员函数,让它们属于类而不属于对象。这个在面向对象部分再详细讲。


🎯 extern vs static 对比

extern static(修饰全局)
作用 声明外部定义的变量/函数 限制变量/函数只在本文件可见
链接属性 外部链接 内部链接
是否分配内存 ❌ 只是声明 ✅ 定义时分配
类比 "我要用别人的东西" "我的东西不让别人用"

🚨 易错点

❌ 错误做法 ✅ 正确做法
extern int a = 10; 又初始化又 extern extern 只是声明,不能初始化;定义写在别的文件
静态局部变量每次调用都重新初始化 static 局部变量只初始化一次,后续保留值
别的文件访问 static 全局变量 static 全局变量只在本文件可见,跨文件用不了
函数声明省略 extern 以为不行 函数声明默认就是 extern 的,写不写都行

💡 口诀:extern 是个声明符,别处定义我来用;static 有三大用,全局函数限本用,局部变量永久存,类成员呀属类用!


8️⃣ 函数的形参与实参

💡 引入:函数调用时发生了什么?

int add(int a, int b) {   // a, b 是形参
    return a + b;
}

int main() {
    int x = 3, y = 5;
    cout << add(x, y);     // x, y 是实参
    return 0;
}
  • 形参(形式参数):函数定义时括号里的参数,是函数内部的局部变量
  • 实参(实际参数):调用函数时传进去的具体值/变量

🎯 值传递:实参把值"拷贝"给形参

C++ 默认是值传递:调用函数时,实参的值被拷贝一份给形参。形参和实参是两个不同的变量,修改形参不会影响实参!

#include <iostream>
using namespace std;

void swap(int a, int b) {   // 形参 a, b
    int t = a;
    a = b;
    b = t;
    cout << "函数内:a = " << a << ", b = " << b << endl;
}

int main() {
    int x = 3, y = 5;        // 实参 x, y
    swap(x, y);
    cout << "函数外:x = " << x << ", y = " << y << endl;
    return 0;
}

输出

函数内:a = 5, b = 3
函数外:x = 3, y = 5

💡 敲黑板:函数内 a 和 b 确实交换了,但 x 和 y 没变!因为值传递是拷贝,形参是实参的"副本",改副本不影响原件。这就是为什么上面的 swap 函数是无效的!


🎯 想修改实参?用引用传递或指针传递!

方法 1:引用传递(推荐,最简洁)

void swap(int &a, int &b) {   // 引用传递,a 和 b 是实参的别名
    int t = a;
    a = b;
    b = t;
}

int main() {
    int x = 3, y = 5;
    swap(x, y);
    cout << x << " " << y << endl;   // 5 3 ✅ 真的交换了!
    return 0;
}

方法 2:指针传递

void swap(int *a, int *b) {   // 传地址
    int t = *a;
    *a = *b;
    *b = t;
}

int main() {
    int x = 3, y = 5;
    swap(&x, &y);              // 传地址
    cout << x << " " << y << endl;   // 5 3 ✅
    return 0;
}

🎯 形参实参对比表

形参 实参
出现位置 函数定义的括号里 函数调用的括号里
本质 函数内部的局部变量 调用时传入的具体值/变量
生命周期 函数调用时创建,结束时销毁 由调用者决定
值传递时 实参的副本 原始数据

🚨 易错点

❌ 错误做法 ✅ 正确做法
以为值传递能修改实参 值传递是拷贝,改形参不影响实参;要改就用引用/指针
形参和实参名字必须一样 名字可以完全不同,只看位置和类型对应
引用传递传常量 swap(3, 5) 非常量引用不能绑定常量,要用 const int& 或传变量
函数参数太多 参数多了可以封装成结构体/类传进去
数组作为形参写成 int a[10] 以为传整个数组 数组传参会退化为指针,sizeof(a) 是指针大小不是数组大小

💡 口诀:形参定义在函数头,实参调用时来投;值传递呀是拷贝,改了形参实参留;想改实参怎么办,引用指针来帮忙!


9️⃣ ^ 是异或,不是求幂!

💡 引入:一个最容易用错的运算符

很多同学从数学过来,看到 ^ 就以为是"次方"(幂运算):

int a = 2 ^ 3;   // 你以为是 2³=8?错!

实际上在 C++ 里,^按位异或运算符,不是幂运算!上面代码的结果是 1,不是 8


🎯 什么是按位异或?

异或(XOR)的规则:相同为 0,不同为 1

A B A ^ B
0 0 0
0 1 1
1 0 1
1 1 0

手动计算:2 ^ 3

  2 = 010(二进制)
  3 = 011(二进制)
  --------
  ^ = 001 = 1

所以 2 ^ 3 = 1,不是 8!


🎯 那怎么求幂?

C++ 没有专门的幂运算符,求幂有两种方法:

方法 1:用 pow 函数(需要 <cmath>

#include <iostream>
#include <cmath>     // pow 在这里
using namespace std;

int main() {
    cout << pow(2, 3) << endl;   // 8(注意返回值是 double)
    cout << (int)pow(2, 3) << endl; // 转成 int
    return 0;
}

⚠️ 注意pow 返回 double,可能有浮点精度问题。整数幂建议用循环或快速幂。

方法 2:循环相乘(整数幂推荐)

int power(int base, int exp) {
    int result = 1;
    for (int i = 0; i < exp; ++i)
        result *= base;
    return result;
}
// power(2, 3) = 8

🎯 异或的神奇用途(竞赛高频!)

异或不只是个运算符,它有很多神奇性质,算法题里经常用:

性质 1:一个数异或自己等于 0

a ^ a = 0

性质 2:一个数异或 0 等于自己

a ^ 0 = a

性质 3:异或满足交换律和结合律

a ^ b = b ^ a
(a ^ b) ^ c = a ^ (b ^ c)

经典应用:不用临时变量交换两个数

int a = 3, b = 5;
a = a ^ b;   // a = 3^5 = 6
b = a ^ b;   // b = 6^5 = 3(b 变成了原来的 a)
a = a ^ b;   // a = 6^3 = 5(a 变成了原来的 b)
// 现在 a=5, b=3,交换完成!不需要临时变量!

经典应用:找出数组中只出现一次的数

一个数组中,只有一个数出现一次,其他都出现两次,找出那个数。

int findOnce(int a[], int n) {
    int result = 0;
    for (int i = 0; i < n; ++i)
        result ^= a[i];   // 出现两次的异或成0抵消,剩下的就是只出现一次的
    return result;
}

🚨 易错点

❌ 错误做法 ✅ 正确做法
2 ^ 3 以为是 8 ^ 是异或,结果是 1;求幂用 pow(2,3) 或循环
pow 求整数幂不处理精度 整数幂用循环或快速幂更可靠
异或交换法用在同一个变量上 a ^= a 会把 a 变成 0,交换时确保是两个不同变量
^&&/`

💡 口诀:C++ 里脱字符,不是求幂是异或;相同为零不同一,位位运算要牢记;自己异或变成零,异或零呀还是己;想要求幂怎么办,pow 函数循环乘!


📝 综合练习

光说不练假把式,动手敲一敲!💪

练习 1 · 引用 🔗

写一个函数,用引用传递交换两个整数的值。

样例输入:3 5
样例输出:5 3

练习 2 · 字符指针输出 📤

以下代码输出什么?

char a[] = "abcdef";
char *p = a + 2;
cout << p << endl;
cout << (void*)p << endl;

练习 3 · 变量作用域 🏠

以下代码能否编译通过?如果不能,怎么改?

for (int i = 0; i < 5; ++i)
    cout << i;
cout << i;

练习 4 · 指针数组 vs 数组指针 🎯

int *p[5]int (*q)[5] 分别是什么?sizeof 各是多少?

练习 5 · 异或 ⚡

不用临时变量,用异或交换 a=7, b=12 的值,写出每一步的结果。


🎉 课堂小结

知识点 一句话记住
& 双重身份 变量前取地址,类型后是引用
字符串常量 vs 字符数组 常量只读,数组可改
cout 输出 char* 输出字符串不是地址,要地址强转 void*
全局 vs 局部 全局活全程,局部活块内,for里的i出循环就没
ifstream/ofstream 读文件/写文件,用法像cin/cout
指针数组 vs 数组指针 *s[]是数组,(*p)[]是指针,括号定乾坤
extern/static extern用别人的,static不让别人用
形参/实参 值传递是拷贝,改形参不影响实参
^ 运算符 是异或不是幂,相同为0不同为1

🌟 概念不怕多,搞懂原理就通透!九个概念反复练,C++ 基础稳如狗!

多敲代码多思考,下次课我们继续进阶~同学们加油!👋


C++ 信息学竞赛训练 · 核心概念专题 · 引用 / 字符串 / 指针 / 作用域 / 文件 / extern-static / 形参实参 / 异或

posted @ 2026-08-23 13:08  飞花阁  阅读(20)  评论(0)    收藏  举报
//雪花飘落效果