一、指针
指针是 C++ 中非常重要的概念。简单来说,指针就是用来保存地址的变量。
普通变量保存的是数据本身,而指针变量保存的是某个变量的内存地址。
1. 指针的基本使用
#include <iostream>
using namespace std;
int main() {
int a = 10;
// p 是一个指针变量,用来保存 int 类型变量的地址
int* p = &a;
// 输出变量 a 的值
cout << "a = " << a << endl;
// 输出变量 a 的地址
cout << "&a = " << &a << endl;
// 输出指针 p 保存的地址
cout << "p = " << p << endl;
// 通过 *p 访问 p 指向的变量
cout << "*p = " << *p << endl;
return 0;
}
在这段代码中:
int* p = &a;
表示定义一个指针变量 p,它保存变量 a 的地址。
*p
表示访问指针 p 指向的那块内存中的值。
2. 指针的作用
指针常见作用有:
1. 间接访问变量
2. 动态申请内存
3. 函数参数传递
4. 操作数组和字符串
5. 实现数据结构,例如链表、树等
3. 指针使用注意点
指针使用时要注意空指针和野指针。
int* p = nullptr; // 推荐使用 nullptr 表示空指针
如果一个指针没有初始化,里面可能是随机地址,这种指针叫野指针。使用野指针可能导致程序崩溃。
错误示例:
int* p; // 没有初始化,p 是野指针
*p = 10; // 错误,可能访问非法内存
正确写法:
int* p = nullptr; // 初始化为空指针
int a = 10;
p = &a; // 让 p 指向变量 a
cout << *p << endl;
4. 指针面试总结
面试时可以这样回答:
指针本质上是一个变量,用来保存内存地址。通过指针可以间接访问某个变量。指针使用灵活,但是也容易出错,例如空指针、野指针、悬空指针等问题。因此在使用指针时要注意初始化,动态申请内存后要及时释放。
二、引用
引用可以理解为变量的别名。给一个变量起了引用之后,引用和原变量操作的是同一块内存。
1. 引用的基本使用
#include <iostream>
using namespace std;
int main() {
int a = 10;
// ref 是 a 的引用,也就是 a 的别名
int& ref = a;
cout << "a = " << a << endl;
cout << "ref = " << ref << endl;
// 修改 ref,相当于修改 a
ref = 20;
cout << "修改后 a = " << a << endl;
cout << "修改后 ref = " << ref << endl;
return 0;
}
输出结果中,a 和 ref 的值都会变成 20。
因为 ref 不是一个新的变量,而是 a 的别名。
2. 引用作为函数参数
引用最常见的作用之一是作为函数参数,避免拷贝,并且可以修改实参。
#include <iostream>
using namespace std;
// 使用引用传参,可以直接修改外部变量
void changeValue(int& x) {
x = 100;
}
int main() {
int a = 10;
changeValue(a);
cout << "a = " << a << endl; // 输出 100
return 0;
}
如果不用引用,而是普通值传递:
void changeValue(int x) {
x = 100;
}
这样只是修改了函数内部的临时变量,不会影响外部的 a。
3. 指针和引用的区别
| 对比项 | 指针 | 引用 |
|---|---|---|
| 本质 | 保存地址的变量 | 变量的别名 |
| 是否可以为空 | 可以为空 | 一般不能为空 |
| 是否需要初始化 | 可以不初始化,但不推荐 | 定义时必须初始化 |
| 是否可以改变指向 | 可以改变指向 | 初始化后不能再引用其他变量 |
| 使用方式 | 通过 *p 访问 |
直接使用 |
| 安全性 | 相对容易出错 | 相对更安全 |
示例代码:
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
// 指针可以改变指向
int* p = &a;
p = &b;
// 引用一旦绑定,就不能再换成其他变量
int& ref = a;
ref = b; // 这里不是让 ref 引用 b,而是把 b 的值赋给 a
cout << "a = " << a << endl; // 输出 20
return 0;
}
注意:
ref = b;
不是让 ref 改为引用 b,而是把 b 的值赋给 ref 所引用的变量 a。
4. 引用面试总结
面试时可以这样回答:
引用是变量的别名,定义时必须初始化,初始化后不能再引用其他变量。引用通常用于函数参数传递,可以避免拷贝,提高效率,也可以让函数内部修改外部变量。相比指针,引用使用起来更简单,也相对更安全。
三、const
const 表示常量,用来修饰变量、指针、函数参数和成员函数。被 const 修饰的内容一般不能被修改。
1. const 修饰普通变量
#include <iostream>
using namespace std;
int main() {
const int a = 10;
// a = 20; // 错误,const 修饰的变量不能修改
cout << a << endl;
return 0;
}
const int a = 10; 表示 a 是一个常量,后面不能再修改。
2. const 修饰指针
const 和指针结合时,面试经常问。
常见有三种写法:
const int* p1; // 指向常量的指针
int* const p2 = &a; // 指针常量
const int* const p3; // 指向常量的指针常量
3. 指向常量的指针
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
// p 指向的值不能通过 p 修改
const int* p = &a;
// *p = 30; // 错误,不能通过 p 修改 a 的值
// p 本身可以改变指向
p = &b;
cout << *p << endl;
return 0;
}
const int* p 的意思是:不能通过 p 修改它指向的值,但是 p 可以指向其他变量。
4. 指针常量
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
// p 是常量指针,必须初始化
int* const p = &a;
// 可以通过 p 修改 a 的值
*p = 30;
// p = &b; // 错误,p 不能改变指向
cout << a << endl;
return 0;
}
int* const p 的意思是:指针 p 本身不能改变指向,但是可以通过 p 修改它指向的值。
5. const 修饰函数参数
如果函数参数不希望被修改,可以使用 const。
#include <iostream>
#include <string>
using namespace std;
// 使用 const 引用传参,既避免拷贝,又防止函数内部修改参数
void printName(const string& name) {
cout << "name = " << name << endl;
// name = "Tom"; // 错误,const 引用不能修改
}
int main() {
string name = "Jack";
printName(name);
return 0;
}
这种写法在 C++ 项目中很常见:
const string& name
它有两个好处:
1. 使用引用,避免拷贝,提高效率。
2. 使用 const,防止函数内部修改数据。
6. const 面试总结
面试时可以这样回答:
const 用来表示不可修改。它可以修饰普通变量、指针、函数参数和成员函数。const 修饰变量时表示变量不能被修改;修饰指针时需要看 const 在 * 的左边还是右边,左边表示指向的值不能改,右边表示指针本身不能改。函数参数中常用 const 引用,既能避免拷贝,又能防止参数被修改。
四、static
static 在 C++ 中有多个作用,不同位置含义不同。面试中常考局部静态变量、静态全局变量、静态成员变量和静态成员函数。
1. static 修饰局部变量
普通局部变量在函数调用结束后就会销毁。
但是 static 局部变量只会初始化一次,并且生命周期一直到程序结束。
#include <iostream>
using namespace std;
void test() {
// static 局部变量只初始化一次
static int count = 0;
count++;
cout << "count = " << count << endl;
}
int main() {
test(); // 输出 1
test(); // 输出 2
test(); // 输出 3
return 0;
}
如果没有 static,每次调用 test() 时,count 都会重新创建并初始化为 0。
2. static 修饰全局变量
// static 修饰全局变量后,该变量只能在当前文件中使用
static int g_num = 10;
static 全局变量的作用域被限制在当前源文件中,其他源文件不能直接访问它。
这样可以减少命名冲突。
3. static 修饰类成员变量
静态成员变量属于整个类,而不是某一个对象。
#include <iostream>
using namespace std;
class Student {
public:
// 静态成员变量声明
static int count;
Student() {
count++;
}
};
// 静态成员变量需要在类外初始化
int Student::count = 0;
int main() {
Student s1;
Student s2;
Student s3;
cout << "学生对象数量:" << Student::count << endl;
return 0;
}
这里 count 属于 Student 类,所有对象共享同一个 count。
4. static 修饰类成员函数
静态成员函数也属于类,而不是某个对象。
#include <iostream>
using namespace std;
class Student {
public:
static int count;
Student() {
count++;
}
// 静态成员函数
static void printCount() {
cout << "学生数量:" << count << endl;
// 静态成员函数中不能直接访问普通成员变量
// 因为它没有 this 指针
}
};
int Student::count = 0;
int main() {
Student s1;
Student s2;
// 可以通过类名直接调用静态成员函数
Student::printCount();
return 0;
}
静态成员函数没有 this 指针,所以不能直接访问普通成员变量和普通成员函数。
5. static 面试总结
面试时可以这样回答:
static 在不同位置有不同含义。修饰局部变量时,变量只初始化一次,生命周期到程序结束;修饰全局变量或函数时,可以限制它们只在当前文件中使用;修饰类成员变量时,该变量属于整个类,所有对象共享;修饰类成员函数时,可以通过类名直接调用,但静态成员函数没有 this 指针,不能直接访问普通成员变量。
五、new/delete
在 C++ 中,new 和 delete 用于动态内存管理。
new 用来在堆区申请内存,并调用构造函数。
delete 用来释放内存,并调用析构函数。
1. new/delete 的基本使用
#include <iostream>
using namespace std;
int main() {
// 在堆区申请一个 int 类型空间,并初始化为 10
int* p = new int(10);
cout << "*p = " << *p << endl;
// 释放堆区内存
delete p;
// 释放后把指针置空,避免悬空指针
p = nullptr;
return 0;
}
注意:
delete p;
p = nullptr;
释放内存后,指针本身还保存着原来的地址,这种指针叫悬空指针。为了安全,通常会把它置为 nullptr。
2. new\[\] 和 delete\[\]
如果申请的是数组,要使用 new[] 和 delete[]。
#include <iostream>
using namespace std;
int main() {
// 动态申请一个长度为 5 的 int 数组
int* arr = new int[5];
// 给数组赋值
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
// 输出数组内容
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
// 释放数组内存,必须使用 delete[]
delete[] arr;
arr = nullptr;
return 0;
}
一定要注意:
new 对应 delete
new[] 对应 delete[]
不能混用。
3. new/delete 和 malloc/free 的区别
| 对比项 | new/delete | malloc/free |
|---|---|---|
| 所属语言 | C++ | C 语言 |
| 是否调用构造函数 | new 会调用构造函数 | malloc 不会 |
| 是否调用析构函数 | delete 会调用析构函数 | free 不会 |
| 返回值类型 | 返回具体类型指针 | 返回 void* |
| 失败处理 | new 失败通常抛异常 | malloc 失败返回 NULL |
| 使用对象 | 更适合 C++ 对象 | 只负责申请原始内存 |
示例代码:
#include <iostream>
#include <cstdlib>
using namespace std;
class Student {
public:
Student() {
cout << "调用构造函数" << endl;
}
~Student() {
cout << "调用析构函数" << endl;
}
};
int main() {
// new 会申请内存,并调用构造函数
Student* s1 = new Student();
// delete 会调用析构函数,并释放内存
delete s1;
// malloc 只申请内存,不会调用构造函数
Student* s2 = (Student*)malloc(sizeof(Student));
// free 只释放内存,不会调用析构函数
free(s2);
return 0;
}
这段代码说明:
new/delete 更适合 C++ 对象。
malloc/free 只负责内存申请和释放,不负责对象构造和析构。
4. new/delete 面试总结
面试时可以这样回答:
new 和 delete 是 C++ 中用于动态内存管理的操作符。new 不仅会申请内存,还会调用对象的构造函数;delete 不仅会释放内存,还会调用对象的析构函数。malloc 和 free 是 C 语言中的库函数,只负责申请和释放原始内存,不会调用构造函数和析构函数。因此在 C++ 中管理对象时,通常使用 new/delete,或者更推荐使用智能指针来减少内存泄漏风险。
六、面试高频问题整理
1. 指针和引用有什么区别?
指针是保存地址的变量,引用是变量的别名。指针可以为空,也可以改变指向;引用定义时必须初始化,并且初始化后不能再引用其他变量。指针使用时需要通过 * 解引用,引用可以直接使用。一般来说,引用更安全,指针更灵活。
2. const 修饰指针时怎么理解?
主要看 const 在 * 的左边还是右边。
const int* p; // 指向的值不能通过 p 修改
int* const p = &a; // 指针本身不能改变指向
const int* const p; // 指向的值不能改,指针本身也不能改
简单记忆:
const 在 * 左边:值不能改。
const 在 * 右边:指针不能改。
3. static 有哪些作用?
static 修饰局部变量时,变量只初始化一次,生命周期到程序结束。
static 修饰全局变量或函数时,可以限制作用域,让它们只能在当前文件使用。
static 修饰类成员变量时,变量属于整个类,所有对象共享。
static 修饰类成员函数时,可以通过类名调用,但不能直接访问普通成员变量,因为静态成员函数没有 this 指针。
4. new/delete 和 malloc/free 有什么区别?
new/delete 是 C++ 的操作符,malloc/free 是 C 语言的库函数。
new 会申请内存并调用构造函数,delete 会调用析构函数并释放内存。
malloc 只申请内存,不会调用构造函数,free 只释放内存,不会调用析构函数。
所以在 C++ 中创建对象时,通常使用 new/delete,而不是 malloc/free。
5. delete 后为什么要把指针置为 nullptr?
delete 释放的是指针指向的内存,但是指针变量本身还保存着原来的地址。如果继续使用这个指针,就可能访问已经释放的内存,形成悬空指针。
所以释放后通常写成:
delete p;
p = nullptr;
这样后面再判断:
if (p != nullptr) {
// 使用 p
}
可以提高程序安全性。
七、总结
本文主要整理了 C++ 面试中非常高频的几个基础知识点:指针、引用、const、static 和 new/delete。
指针本质上是保存地址的变量,使用灵活,但容易出现空指针、野指针和悬空指针等问题。
引用是变量的别名,定义时必须初始化,使用起来比指针更简单,常用于函数参数传递,可以避免拷贝,提高效率。
const 用来表示不可修改,常用于修饰变量、指针和函数参数。面试中重点掌握 const 指针和指针常量的区别。
static 在不同位置有不同含义,可以修饰局部变量、全局变量、类成员变量和类成员函数。面试中要重点理解 static 局部变量只初始化一次,以及 static 成员属于类而不是对象。
new/delete 用于 C++ 动态内存管理。new 会申请内存并调用构造函数,delete 会调用析构函数并释放内存。new 和 delete 要配对使用,new\[\] 和 delete\[\] 也要配对使用。
简单记忆:
指针:保存地址,灵活但容易出错。
引用:变量别名,必须初始化,更安全。
const:限制修改,提高代码安全性。
static:改变生命周期或作用域,也可表示类共享成员。
new/delete:动态申请和释放内存,会调用构造和析构函数。
面试中回答这些基础问题时,不要只背概念,最好能结合代码说明它们的使用场景和区别。