1. 什么是 C++?C++ 的优点是什么?
C++简介及其优点
C++是一种通用的编程语言,最初由Bjarne Stroustrup于1979年在贝尔实验室开发,目的是作为C语言的扩展,支持面向对象的编程(OOP)。C++支持过程式编程、面向对象编程、泛型编程等多种编程范式。
C++的主要特点:
- 面向对象:C++引入了类(class)和对象(object)的概念,支持封装、继承和多态等面向对象的特性。
- 多范式:C++不仅支持面向对象编程,还支持过程式编程和泛型编程,灵活性非常高。
- 低级操作:C++允许开发者直接操作硬件和内存,能够提供高效的性能,适用于需要高性能的应用场景。
- 标准库:C++提供了强大的标准库(STL,Standard Template Library),包括容器类、算法、迭代器等,使得编程更加高效和简洁。
- 高性能:C++编译后的代码执行速度非常快,适合进行性能要求高的系统开发,如操作系统、游戏引擎、实时系统等。
C++的优点:
-
性能高效:
C++是一种编译语言,通过编译生成本地机器码,运行时效率高。相比于解释型语言(如Python),C++可以更直接地操作硬件和内存。 -
灵活性强:
C++支持多种编程范式(面向对象、过程式、泛型编程),能够根据项目需求选择最合适的编程方法。 -
面向对象编程(OOP):
C++支持类和对象的概念,能够更好地管理复杂系统。通过封装、继承和多态,C++帮助开发者构建可扩展和易维护的代码。 -
内存控制:
C++允许开发者手动管理内存(通过new
和delete
),在需要时能够直接优化内存分配和回收。虽然这增加了编程的复杂性,但也提供了更高的控制权。 -
广泛应用:
C++在各个领域都有广泛的应用,如操作系统开发、游戏开发、嵌入式系统、金融系统、图像处理等。它的高效性和灵活性使其在很多行业中都占有重要地位。 -
强大的标准库(STL):
C++的标准模板库(STL)提供了大量的预先构建的数据结构和算法(如向量、链表、映射、排序、查找等),极大地提高了开发效率。 -
跨平台支持:
C++的代码能够在不同的平台上运行,只要对应平台提供了C++的编译器。对于需要跨平台开发的项目,C++是一个非常理想的选择。
2. C++ 中有哪些数据类型?
C++ 中主要的数据类型有:
-
基本数据类型
int
:整数类型float
:单精度浮点数double
:双精度浮点数char
:字符类型bool
:布尔类型(true
或false
)
-
修饰符
short
:短整型long
:长整型long long
:更长的整型unsigned
:无符号类型(unsigned int
等)signed
:有符号类型(默认为signed
)
-
派生数据类型
- 数组:同一数据类型元素的集合
- 指针:存储地址的变量
- 引用:变量的别名
-
抽象数据类型
struct
:结构体class
:类union
:共用体enum
:枚举类型
-
空类型
void
:无类型,通常用于函数返回类型
-
类型别名
typedef
或using
:为现有类型创建别名
3. 定义‘std’?
‘std’ 也叫做标准命名空间,或者可以理解为命名空间。命令 using namespace std
会告知编译器将 std 命名空间中的所有内容引入到全局命名空间中,这样我们就可以直接使用 cout
和 cin
,而不需要写 std::cout
和 std::cin
。
4. 什么是引用?
引用是一个已初始化的别名,用于指向一个已有的变量。通过引用,可以直接操作原始变量的值。
特点:
- 必须在声明时初始化,且不能改变引用的对象。
- 引用与原变量共享同一内存地址。
- 常用于函数参数传递,避免拷贝,提高效率。
示例:
int a = 10;
int& ref = a; // ref是a的引用
ref = 20; // 通过引用修改a的值
// a的值现在是20
常量引用(const引用)用于引用不可修改的对象。
const int& ref = a; // ref是a的常量引用
5. 什么是值传递和引用传递?
1. 值传递(Pass by Value)
- 定义:函数接收的是实参的副本,修改副本不会影响原始数据。
- 特点:会复制数据,性能开销较大,尤其是传递大型对象时。
示例:
void func(int x) {
x = 20; // 修改x不会影响实参
}
int main() {
int a = 10;
func(a);
// a的值仍为10
}
2. 引用传递(Pass by Reference)
定义:函数接收的是实参的引用,修改参数会直接修改实参。
特点:避免了数据拷贝,常用于修改实参或传递大型对象。
void func(int& x) {
x = 20; // 修改x会影响实参
}
int main() {
int a = 10;
func(a);
// a的值变为20
}
6. 什么是 C++ 中的“token”?
token 是程序中最小的独立元素,编译器可以理解它。一个 token 包括以下内容:
- 关键词:对编译器具有特殊意义的词。
- 标识符:持有唯一值/身份的元素。
- 常量:在程序执行过程中始终保持不变的值。
- 字符串:包含同质数据序列。
- 特殊符号:具有特殊含义,不能用于其他目的的符号,如 [] () {} ; * = #。
- 操作符:用于对操作数执行运算的符号。
7. C 和 C++ 之间的区别?
C | C++ |
---|---|
是一种过程式编程语言,不支持类和对象 | 是过程式与面向对象相结合的编程语言,支持类和对象 |
不支持 OOPs 概念,如多态、数据抽象、封装等 | 支持多种 OOPs 概念 |
不支持函数重载和操作符重载 | 支持函数重载和操作符重载 |
是一种面向函数的语言 | 是一种面向对象的语言 |
8. struct
和 class
有什么区别?
Aspect | struct | class |
---|---|---|
默认访问修饰符 | 成员默认是公共的 | 成员默认是私有的 |
内存分配 | 可以分配在栈或堆上 | 可以分配在栈或堆上 |
继承 | 支持继承(公共、保护或私有访问) | 支持继承(公共、保护或私有访问) |
用途 | 通常用于简单的数据结构或数据组合 | 适用于复杂对象,可能包括方法、构造函数和析构函数 |
9. 引用和指针有什么区别?
引用 | 指针 |
---|---|
引用的值不能重新分配 | 指针的值可以重新分配 |
引用不能持有空值,必须指向一个已存在的值 | 指针可以指向空值,被称为 nullptr 或空指针 |
访问类/结构体成员时使用 ‘.’ | 访问类/结构体成员时使用 ‘->’ |
引用的内存位置可以直接访问 | 指针的内存位置必须通过解引用 * 来访问 |
10. 函数重载和操作符重载有什么区别?
函数重载 | 操作符重载 |
---|---|
定义多个同名函数,但每个函数的参数不同 | 重新定义现有操作符的意义 |
通过改变函数参数的数量或类型来实现重载 | 使已有操作符在特定上下文中具有不同的行为 |
例如:int add(int a, int b); int add(double a, double b); | 例如:class Complex { Complex operator+(const Complex& other); } |
11. 数组和列表有什么区别?
数组 | 列表 |
---|---|
数组是存储在连续内存位置的同质数据类型,大小固定 | 列表是通过指针将元素连接在一起,大小动态 |
数组的大小是静态的,创建后不能改变 | 列表是动态的,可以根据需要调整大小 |
数组占用的内存较少 | 列表占用更多内存,因为它需要存储元素和指针的内存 |
12. while
循环和 do-while
循环有什么区别?
while 循环 | do-while 循环 |
---|---|
while 是一种入口控制循环,先判断条件,再执行循环体 | do-while 是一种出口控制循环,先执行循环体,后判断条件 |
如果条件不成立,循环体将不会执行 | 即使条件不成立,循环体也会至少执行一次 |
13. 前缀和后缀运算符有什么区别?
前缀运算符 | 后缀运算符 |
---|---|
运算符位于操作数前面,先执行运算 | 运算符位于操作数后面,先返回操作数再执行运算 |
前缀递增 ++i 执行时是右到左关联 | 后缀递增 i++ 执行时是左到右关联 |
14. new
和 malloc()
有什么区别?
new | malloc() |
---|---|
new 是一个运算符,用于分配内存 | malloc 是一个函数,用于分配内存 |
new 会调用构造函数 | malloc 不会调用构造函数 |
new 的效率比 malloc 高 | malloc 的效率较低 |
new 返回特定数据类型的指针 | malloc 返回 void* 类型的指针 |
15. 虚函数和纯虚函数有什么区别?
虚函数 | 纯虚函数 |
---|---|
虚函数是基类的成员函数,可以在派生类中重定义 | 纯虚函数是基类的成员函数,在基类中只声明,在派生类中定义,防止基类成为抽象类 |
基类中的虚函数可以有定义 | 纯虚函数在基类中没有定义,且声明时使用 = 0 |
16. C++ 中的类和对象是什么?
- 类(Class)
- 类是 C++ 中用来定义对象的模板,包含数据成员(变量)和成员函数(方法)。
- 类描述了一组具有相同属性和行为的对象。
示例:
class Car {
public:
// 成员变量(属性)
string brand;
int year;
// 成员函数(方法)
void start() {
cout << "The car is starting." << endl;
}
};
- 对象(Object)
- 对象是类的一个实例,包含类中定义的数据和方法。
- 每个对象根据类的定义在内存中创建,并且每个对象都有自己的数据。
示例:
int main() {
// 创建对象 myCar
Car myCar;
myCar.brand = "Toyota";
myCar.year = 2020;
myCar.start(); // 调用成员函数
}
- 对象通过类的构造和行为来实现具体功能。可以创建多个对象,每个对象独立存在,互不影响。
17. 什么是函数重写?
-
定义
- 函数重写是子类重新定义父类中已有的虚函数的过程。
- 重写的函数在子类中具有与父类函数相同的名称、返回类型和参数列表,但实现内容不同。
-
关键点
- 虚函数(Virtual Function):父类中的函数必须是虚函数(使用
virtual
关键字声明),以允许在子类中进行重写。 - 子类实现:子类必须重新定义该函数,提供不同的实现。
- 多态:函数重写是实现运行时多态的关键。
- 虚函数(Virtual Function):父类中的函数必须是虚函数(使用
-
示例
#include <iostream> using namespace std; class Animal { public: virtual void sound() { // 父类虚函数 cout << "Animal makes a sound" << endl; } }; class Dog : public Animal { public: void sound() override { // 子类重写父类虚函数 cout << "Dog barks" << endl; } }; int main() { Animal* animal = new Dog(); animal->sound(); // 输出 "Dog barks",多态表现 delete animal; return 0; }
-
注意事项
- 子类重写的函数应使用
override
关键字(可选,但推荐),以便编译器检查是否正确重写了父类的虚函数。 - 如果父类函数没有被声明为虚函数,子类的函数将不会覆盖父类函数,而是隐藏父类的同名函数。
- 子类重写的函数应使用
18. C++ 中的 OOPs 概念有哪些?
C++ 中的 OOPs 概念包括:
- 类(Classes):是用户定义的数据类型
- 对象(Objects):是类的实例
- 抽象(Abstraction):显示必要的细节,隐藏不必要的部分
- 封装(Encapsulation):将数据和方法封装在一起
- 继承(Inheritance):类从其他类中继承属性和特性
- 多态(Polymorphism):同一函数在不同情况下有不同的表现
19. 解释继承
-
定义
- 继承是面向对象编程中的一种机制,允许一个类(子类)继承另一个类(父类)的属性和行为(成员变量和成员函数)。
- 子类可以使用父类的公有成员,还可以扩展或重写父类的功能。
-
关键点
- 继承关系:子类通过继承关系可以获得父类的属性和方法,继承是“is-a”关系。
- 访问权限:继承有不同的访问权限,如公有继承、保护继承和私有继承。
- 构造函数和析构函数:父类的构造函数不会被继承,但会在子类对象创建时调用。父类的析构函数也会在子类对象销毁时调用。
-
示例
#include <iostream> using namespace std; // 父类 class Animal { public: void eat() { cout << "Animal is eating" << endl; } }; // 子类继承父类 class Dog : public Animal { public: void bark() { cout << "Dog is barking" << endl; } }; int main() { Dog dog; dog.eat(); // 子类可以访问父类的成员 dog.bark(); // 子类自己的方法 return 0; }
-
继承类型
- 公有继承(public inheritance):子类继承父类的公有成员和保护成员,且保持原有的访问权限。
- 保护继承(protected inheritance):子类继承父类的公有成员和保护成员,变为保护成员。
- 私有继承(private inheritance):子类继承父类的所有成员,所有成员都变为私有。
20. 何时使用多重继承?
多重继承意味着一个派生类可以继承多个基类。当一个派生类需要结合多个父类的属性和行为时,可以使用多重继承。
- 注意菱形继承问题:多个父类继承同一个基类时,可能会导致重复继承。
- 使用虚继承(virtual inheritance) 可以避免菱形继承带来的二义性和冗余。
21. 什么是虚继承?
虚继承是一种确保派生类只继承一个基类成员变量的技术。当我们处理多重继承时,虚继承可以避免基类的多重实例出现在继承层次中。
22. 什么是多态?
多态是指同一操作在不同对象上有不同表现形式。在 C++ 中,多态分为两种:
- 编译时多态:函数重载和操作符重载
- 运行时多态:函数重写和虚函数
23. 编译时多态和运行时多态的区别?
编译时多态 | 运行时多态 |
---|---|
发生在程序编译时,执行较快 | 发生在程序运行时,执行较慢 |
通过函数重载和操作符重载实现 | 通过虚函数和函数重写实现 |
24. 构造函数是什么?
构造函数是类的特殊成员函数,名称与类名相同,用于初始化类的对象。构造函数有三种类型:
- 默认构造函数:不带参数的构造函数,编译器会自动调用它。
- 参数化构造函数:带参数的构造函数,必须显式调用。
- 复制构造函数:用于通过同一类的另一个对象来初始化一个对象。
25. 析构函数是什么?
析构函数是类的成员函数,当类的对象超出作用域时,析构函数会删除对象并释放资源。析构函数的名称与类名相同,但前面有一个波浪符 ~
。
26. 虚析构函数是什么?
虚析构函数确保在通过基类指针删除派生类对象时,派生类的析构函数先被调用,从而防止内存泄漏。
27. 析构函数能否重载?如果能,请解释。
不能重载析构函数。在 C++ 中,每个类只能有一个析构函数,而且析构函数不能接受任何参数。
28. 指针可以进行哪些操作?
指针可以进行以下操作:
- 指针的增/减
- 将整数与指针相加/减
- 比较相同类型的指针
29. “delete” 操作符的作用是什么?
delete
操作符用于删除通过 new
分配的动态内存,释放内存并清除对象。
30. delete[]
和 delete
的区别是什么?
delete[] | delete |
---|---|
用于删除数组对象 | 用于删除单个对象 |
可以调用多个析构函数 | 只能调用一个析构函数 |
31. 什么是友元类和友元函数?
友元类可以访问另一个类的私有和保护成员。友元函数可以访问类的私有、保护和公共数据成员。它不是类的成员函数,可以在类外部调用。
32. 溢出错误是什么?
溢出错误发生在数据超出了数据类型所能表示的范围时。例如,int
类型的范围是 -2,147,483,648 到 2,147,483,647,如果分配一个大于上限的值,就会发生溢出错误。
33. ::
(作用域解析运算符)做什么?
作用域解析运算符 ::
用于访问类外部的全局变量,或引用类成员函数、命名空间中的元素。
34. C++ 中的访问修饰符有哪些?
C++ 中的访问修饰符有三种:
- private:类外部无法访问
- protected:只能在派生类中访问
- public:类外部可以访问
35. 可以没有 main
函数编译程序吗?
是的,可以编译没有 main
函数的程序。例如,使用宏来定义 main
函数。
#include <stdio.h>
#define fun main
int fun(void) {
printf("GeeksforGeeks");
return 0;
}
36. 什么是 STL?
-
定义
- STL(Standard Template Library)是 C++ 标准库的一部分,提供了常用的算法和数据结构模板。
- STL 主要包括容器、算法、迭代器和函数对象等组件,用于提高开发效率和代码复用性。
-
STL 组件
- 容器(Containers):用于存储数据的对象,如
vector
,list
,map
,set
等。- 顺序容器:如
vector
,deque
,list
,按顺序存储元素。 - 关联容器:如
set
,map
,unordered_set
,unordered_map
,按特定顺序存储元素。 - 适配器容器:如
stack
,queue
,priority_queue
,为其他容器提供特定功能。
- 顺序容器:如
- 算法(Algorithms):用于操作容器的数据,如排序、查找、修改等,常见的算法有
sort
,find
,reverse
等。 - 迭代器(Iterators):用于访问容器中的元素,可以通过迭代器来遍历容器。迭代器类似于指针。
- 函数对象(Function Objects):通过重载
operator()
实现的可调用对象,常用于 STL 算法中,如std::greater
,std::less
。
- 容器(Containers):用于存储数据的对象,如
37. 定义内联函数。C++ 中能有递归的内联函数吗?
内联函数是通过 inline 关键字声明的函数,它的目的是减少函数调用时的开销。内联函数会在调用时将函数体直接插入到调用点,而不是进行常规的函数调用。这能减少函数调用的性能开销,尤其适合一些简单且频繁调用的函数。
递归的内联函数:
递归函数通常不能作为内联函数,因为递归调用需要多次执行,编译器无法预知每次递归的调用情况,因此不能提前将其展开为内联代码。尽管可以声明递归函数为 inline,但编译器通常不会对其进行内联优化。
inline int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 递归调用
}
尽管递归函数可以声明为内联,但编译器通常会拒绝对其进行内联处理,因为递归的调用结构复杂且不确定。
38. 抽象类是什么?何时使用?
抽象类是一个不能实例化的类,它包含至少一个纯虚函数。抽象类用于提供一个统一的接口,但不提供具体实现,通常用作基类。
39. 静态数据成员和静态成员函数是什么?
静态数据成员是属于类的成员,而不是对象的成员,它在程序开始时初始化并在所有对象之间共享。静态成员函数可以访问类的静态成员。
40. volatile 关键字的主要用途是什么?
volatile 关键字用于告诉编译器该变量可能会随时改变,防止编译器优化该变量,通常用于与硬件或信号处理程序交互时。