🧠 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 只是赋值 |
&a 和 int &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 *p 和 char a[] 完全一样 |
p 是指针(4/8字节),a 是数组(整个字符串大小) |
💡 口诀:字符串常量只读区,想改它呀没门去;字符数组栈上住,增删改查都自如;常量前面加 const,编译器来帮你护!
3️⃣ cout 输出字符指针:输出的是字符串,不是地址!
💡 引入:一个反直觉的特性
看这段代码,你觉得输出什么?
char a[] = "hello";
char *p = a;
cout << p << endl; // 输出什么?
很多同学以为会输出一个地址(类似 0x61ff0c),但实际上输出的是:
hello
为什么?因为 cout 对 char* 类型做了特殊处理:它不会输出指针的值(地址),而是把这个地址当作字符串的起始位置,从这个位置开始逐个输出字符,直到遇到结束符 \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 文件时,就需要 extern 和 static 来管理变量和函数的可见性。
🎯 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 / 形参实参 / 异或

浙公网安备 33010602011771号