八股文
1.类和结构体的区别
在 C++ 中,类(class)和结构体(struct)在语法上几乎是相同的,唯一的区别是默认的访问权限。在结构体中,默认的访问权限是公有的(public),而在类中,默认的访问权限是私有的(private)。默认继承方式不同。
尽管结构体在设计上更偏向于数据的聚合,而类更倾向于实现封装、继承和多态等面向对象编程的特性,但实际上,结构体也可以具有封装、继承和多态的特性。
2. 字符串长度
如果使用字符数组来存储字符串,通常需要考虑字符串的结尾标志符
(空字符,ASCII码为0)。在C中,字符串是以空字符
结尾的。
因此,字符串的实际长度是字符数组长度减去1。这是因为最后一个字符用于存储空字符
,用来表示字符串的结束。这个空字符告诉程序字符串的实际长度。
例如:
#include <iostream> #include <cstring> int main() { char str[10]; // 字符数组长度为10 // 将字符串 "Hello" 复制到字符数组中 strcpy(str, "Hello"); // 计算字符串长度 int length = strlen(str); // 输出字符串和长度 std::cout << "String: " << str << std::endl; std::cout << "Length: " << length << std::endl; return 0; }
在这个例子中,字符数组
str
的长度是10,但实际存储的字符串是 “Hello”,字符串长度是5。因为字符数组的最后一个位置被空字符占用。如果字符串的长度达到数组的最大长度,需要确保数组足够大以容纳字符串和结尾的空字符。
3.宏定义
#define MAX_PRO1(A, B) ({
int a = A;
int b = B;
a > b ? a : b;
})
#define MAX_PRO3(A, B) ({
typeof(A) a = A;
typeof(B) b = B;
(void) (&a == &b);
a > b ? a : b;
})
4.动态扩容数组
#include <iostream>
template <typename T>
class DynamicArray {
private:
T* data; // 指向动态分配数组的指针
size_t size; // 数组的当前元素个数
size_t capacity; // 数组的当前容量
public:
// 构造函数
DynamicArray() : data(nullptr), size(0), capacity(0) {}
// 析构函数
~DynamicArray() {
delete[] data;
}
// 获取数组大小
size_t getSize() const {
return size;
}
// 添加元素到数组末尾
void pushBack(const T& value) {
if (size == capacity) {
// 如果数组满了,进行扩容
reserve(capacity == 0 ? 1 : 2 * capacity);
}
data[size++] = value;
}
// 获取数组元素
T& operator[](size_t index) {
if (index < size) {
return data[index];
} else {
throw std::out_of_range("Index out of range");
}
}
private:
// 扩容数组
void reserve(size_t newCapacity) {
T* newData = new T[newCapacity];
for (size_t i = 0; i < size; ++i) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
};
int main() {
DynamicArray<int> dynamicArray;
for (int i = 0; i < 10; ++i) {
dynamicArray.pushBack(i);
}
for (size_t i = 0; i < dynamicArray.getSize(); ++i) {
std::cout << dynamicArray[i] << " ";
}
return 0;
}
5.C++函数多个返回值 以及默认值的情况。
#include <iostream> // 函数声明时为参数提供默认值 void greet(std::string name = "Guest", int age = 0) { std::cout << "Hello, " << name << "! Age: " << age << std::endl; } int main() { // 调用函数时不传递参数,使用默认值 greet(); // 输出: Hello, Guest! Age: 0 // 调用函数时传递部分参数,其他参数使用默认值 greet("Alice"); // 输出: Hello, Alice! Age: 0 // 调用函数时传递所有参数,不使用默认值 greet("Bob", 25); // 输出: Hello, Bob! Age: 25 return 0; }
在C++中,一个函数一般只能有一个返回值。这是因为C++语法规定,函数的返回类型(通过函数声明或定义中的返回类型指定)只能是一个类型,而函数执行完毕后只能返回一个值。
使用结构体或类: 可以定义一个结构体或类,将多个值打包在一个结构体或类的对象中,然后将这个对象作为函数的返回值。这样,实际上就可以返回多个值了。
struct MultipleValues { int value1; double value2; }; MultipleValues myFunction() { MultipleValues result; result.value1 = 42; result.value2 = 3.14; return result; }
使用引用参数或指针参数: 函数可以通过引用参数或指针参数来修改传递给它的变量的值,从而实现对多个值的修改。
void myFunction(int &value1, double &value2) { value1 = 42; value2 = 3.14; } int main() { int intValue; double doubleValue; myFunction(intValue, doubleValue); // 现在 intValue 和 doubleValue 包含了 myFunction 中设定的值 return 0; }
6.机器字长 存储字长
32位机通常指的是机器字长(Machine Word Length)为32位。机器字长是指在一台计算机上,CPU一次能够处理的二进制位数,也就是它的寄存器宽度。这影响了CPU能够处理的数据的大小和寻址范围。
存储字长(Storage Word Length)是指计算机在内存中一次读取或写入的二进制位数。虽然这两个概念有相似之处,但也存在区别:
总的来说,机器字长主要涉及到处理器的寄存器宽度,而存储字长则涉及到内存中的数据单元大小。在32位机器上,这两者通常是相同的,但在其他架构中,可能存在不同的存储和机器字长。
不同位数的计算机体系结构可能会有不同的数据类型位数。计算机中的数据类型通常与机器字长相关,而机器字长是指计算机处理器一次能够处理的二进制位数,即寄存器宽度。
下面是一些常见的计算机体系结构和它们对应的典型的数据类型位数:
-
32位计算机:
-
64位计算机:
-
16位计算机:
具体的数据类型位数取决于编译器和计算机架构。C++标准规定了最小的数据类型位数,但允许编译器选择更大的位数。在不同的平台上,可以通过查看编译器的文档或使用sizeof
运算符来确定各种数据类型的确切位数。例如:
#include <iostream>
int main() {
std::cout << "Size of int: " << sizeof(int) * 8 << " bits" << std::endl;
std::cout << "Size of long: " << sizeof(long) * 8 << " bits" << std::endl;
// 可以输出其他数据类型的位数
return 0;
}
常量指针定义:又叫常指针(常量的指针),即这是个指向常量的指针,这个常量是指针的值(地址),而不是地址指向的值。
关键点:
- 常量指针指向的对象不能通过这个指针来修改,可是仍然可以通过原来的声明修改;
- 常量指针可以被赋值为变量的地址,之所以叫常量指针,是限制了通过这个指针修改变量的值;
- 指针还可以指向别处,因为指针本身只是个变量,可以指向任意地址。
指针常量定义:本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值因为是常量,所以不能被赋值。
关键点:
int main() {
int a = 10;
int b = 10;
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改 常量指针 指向常量的指针,指向的地址内容不可更改
const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改 指针常量
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确
//const既修饰指针又修饰常量
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误
system("pause");
return 0;
}
#####8. malloc free 以及 new delete的区别
#####malloc
和free
是C语言中用于内存分配和释放的函数,而new
和delete
是C++中对应的运算符。以下是它们之间的主要区别:
malloc
和 free
(C语言)
new
和 delete
(C++语言)
总体而言,new
和 delete
提供了更多的功能和类型安全性,适用于C++中的对象。而 malloc
和 free
是C语言中的函数,更低级,用于简单的内存分配和释放。在C++中,推荐使用 new
和 delete
来管理动态内存。
先简述共同点,再从类型安全,初始化,分配大小,以及对象的创建和销毁的角度说明
9.左值和右值
左值(Lvalue)和右值(Rvalue)是C++中的基本概念,它们与表达式和对象的值的生命周期相关。以下是一个简洁而全面的回答:
左值(Lvalue):
- 标识符: 左值是一个具有标识符的表达式,可以出现在赋值语句的左侧。
- 持久性: 具有持久性的对象,其生命周期可以长时间存在。
- 可寻址: 可以取地址的表达式,即可以使用取地址运算符
&
获取其地址。
右值(Rvalue):
- 临时性: 右值是临时性的,通常是在表达式求值后立即被销毁的临时对象。
- 不能寻址: 通常不能取地址的表达式,尝试使用
&
取地址会导致编译错误。 - 常常出现在赋值语句的右侧: 右值通常出现在赋值语句的右侧,作为赋值的源。
回答示例:
左值和右值是C++中用于描述表达式的术语。左值是具有标识符的表达式,具有持久性,可以取地址。典型的例子包括变量和通过引用访问的对象。右值是临时性的,通常在表达式求值后立即被销毁。它通常出现在赋值语句的右侧,是表达式的计算结果。***左值和右值的区别在于它们的生命周期和是否可寻址。***在C++11及更高版本中,右值引用(Rvalue Reference)的引入进一步强调了右值的重要性,例如移动语义的实现。
关于右值引用,可以补充说明在C++11之后,引入了右值引用这个新的类型,可以通过 &&
定义。右值引用允许我们更有效地处理右值,如实现移动语义,提高性能。
#####10. 四大类型转换
在C++中,有四种基本的类型转换,通常被称为 “四大转换”,它们分别是:静态转换(static_cast
)、动态转换(dynamic_cast
)、常量转换(const_cast
)、重新解释转换(reinterpret_cast
)。这些转换提供了不同的功能,涉及到不同的情景和要求。以下是一个简洁的回答:
int num = static_cast<int>(3.14);
Base* basePtr = static_cast<Base*>(derivedPtr);
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr != nullptr) {
// 安全地使用 derivedPtr
}
const int value = 42;
int* mutableValue = const_cast<int*>(&value);
int intValue = 10;
double* doublePtr = reinterpret_cast<double*>(&intValue);
在回答时,可以简要介绍每一种转换的主要用途和注意事项。强调在使用这些转换时需要慎重,最好在确保安全性和可维护性的前提下使用。
11.函数重载
12.面向对象的三大特性以及好处
在C++中,面向对象编程(OOP)的三大特性分别是封装(Encapsulation)、继承(Inheritance)、和多态(Polymorphism)。
-
多态(Polymorphism):
总体而言,面向对象编程的这三大特性有助于提高代码的可维护性、可读性、重用性和灵活性。它们提供了一种结构化的方法,使得程序员能够更好地组织和设计代码,同时通过封装、继承和多态实现抽象、简化和模块化。
13.构造函数与析构函数
14.构造函数的分类以及深拷贝与浅拷贝
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
括号法
显示法
隐式转换法
深浅拷贝
为了避免浅拷贝时,堆内存数据重复释放
深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是涉及到对象拷贝的两个概念,主要用于描述在复制对象时如何处理对象内部的数据。以下是它们的区别:
class ShallowCopyExample {
public:
int* data;
// 构造函数
ShallowCopyExample(int value) {
data = new int(value);
}
// 拷贝构造函数(浅拷贝)
ShallowCopyExample(const ShallowCopyExample& other) {
data = other.data; // 浅拷贝,共享同一块内存
}
// 析构函数
~ShallowCopyExample() {
delete data;
}
};
- 深拷贝(Deep Copy):
class DeepCopyExample {
public:
int* data;
// 构造函数
DeepCopyExample(int value) {
data = new int(value);
}
// 拷贝构造函数(深拷贝)
DeepCopyExample(const DeepCopyExample& other) {
data = new int(*(other.data)); // 深拷贝,分配新的内存
}
// 析构函数
~DeepCopyExample() {
delete data;
}
};
在实际编程中,当类包含动态分配的资源时,经常需要谨慎考虑深拷贝和浅拷贝的问题,以确保对象之间的独立性。
深拷贝和浅拷贝都是初始化对象属性的方式。浅拷贝仅复制对象的值,而不复制对象动态分配的资源。深拷贝既会拷贝对象的值也会拷贝对象动态分配的资源,确保新对象是原对象的独立副本。如果一个类在堆区有成员变量,使用深拷贝是必要的,因为浅拷贝会导致多个对象共享同一块内存,可能引发潜在的问题。深拷贝的原理是重新在堆区开辟一个空间,并将原对象的值和动态分配的资源复制到新的空间中,确保对象的独立性和完整性。
#####15.静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
16.c++的内存模型
成员变量与成员方法分开存储
好处:
2.节省内存:属性表明对象的状态,而成员函数是所有对象共享的。
3.生命周期不同
4.权限访问控制。
17.友元
友元是一种在C++中的特殊机制,它允许一个函数或者类访问另一个类的私有成员。友元可以提供对类的某些成员的访问权限,打破了类的封装性。友元可以是一个函数,一个类,或者一个类中的成员函数。然而,要谨慎使用友元,因为过度使用可能导致代码的复杂性增加,破坏了封装性,使代码变得难以理解和维护。因此,在设计中应该限制友元的使用,确保它只在必要的情况下被使用。”
#####18.运算符重载
运算符重载是C++中的一项特性,它允许程序员重新定义或者扩展基本的运算符,使其能够用于自定义数据类型。基本的运算符通常仅对内置数据类型进行操作,但运算符重载使得我们可以定义类的成员函数或全局函数,以实现对自定义数据类型的运算。通过运算符重载,我们能够提高代码的可读性和表达性,使得自定义类型的对象可以像内置类型一样进行直观的运算操作。这种能力允许我们在自定义类型上使用类似于 +
、-
、*
等运算符,从而使代码更加简洁、优雅,并且更符合直觉。
子类继承父类有三种方式,公有继承,受保护的继承,私有继承。无论哪种继承方式,子类均可以访问到父类的非私有成员。但子类的继承的成员的可见性会发生变化。如果是共有继承,那么可见性与父类一致,如果是受保护的继承,则除了父类的私有成员,其他的可见性都变成了受保护,如果是私有继承,那么可见性均变为私有。
1.受保护权限和所有权限的区别主要在于派生类继承对父类成员的可见性的区别,而类外的可见性没有区别
2.继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
#####20.虚继承
虚继承是C++中用于解决菱形继承问题(Diamond Problem)的一种技术。当一个类被虚继承时,它的派生类只会继承一份基类的实例,而不是多份。这样可以防止由于多次继承同一基类而导致的二义性和资源浪费。
#####21.多态的概念
多态是面向对象编程的一项重要特性,它允许通过父类指针或引用来访问派生类对象,通过同一个父类接口可以调用不同子类对象的属性或方法。多态的实现条件主要有三个:首先,父类必须有虚函数;其次,子类必须重写这个虚函数;最后,我们通过父类指针或引用指向子类对象。
多态的核心在于动态绑定,这意味着在运行时才确定调用哪个函数。当我们通过父类指针调用虚函数时,程序会在运行时根据指针所指的具体对象类型来动态地确定调用的是哪个函数。这样,同一份代码可以处理不同类型的对象,从而提高了代码的灵活性和可扩展性
22.虚函数指针 虚函数表
在包含虚函数的类中,编译器在对象的内存布局中插入了一个称为虚函数指针(vptr)的指针。这个指针指向该类的虚函数表,虚函数表是一个数组,包含了该类及其基类的虚函数地址。对于每一个具体的对象,虚函数指针都会被初始化为指向其所属类的虚函数表。
在运行时,当我们通过基类指针或引用调用虚函数时,程序会使用虚函数指针来访问虚函数表。动态绑定的过程发生在运行时,通过查询虚函数表确定到底调用哪个函数。这意味着,即使是通过基类指针或引用调用虚函数,实际调用的是与对象的实际类型相对应的函数,而不是基类中的函数。
***总的来说,虚函数指针和虚函数表提供了一种在运行时动态地确定调用哪个虚函数的机制,实现了多态性 ***
23.模板的概念
模板包括函数模板与类模板 可以将类型参数化,这允许我们编写出与数据类型无关的代码,增强了代码复用与开发效率 同时模板也是类型安全的
#####24. 友元
#include <iostream>
using namespace std;
/*
定义一个类B,然后B想要访问A里面的私有成员
那么
1.应该先声明B,因为B要在A中声明友元
2.申明并定义A
3.定义B
*/
class B;
class A {
private:
int a;
public:
A(int a) {
this->a = a;
}
friend class B;
};
class B {
private:
int b;
public:
B(int b) {
this->b = b;
}
void print_A(const A& a) {
cout << a.a;
}
};
int main() {
A a(50);
B b(20);
b.print_A(a);
return 0;
}
函数作为友元
*** 全局函数作为友元时,友元声明必须在类的内部进行***
#include <iostream>
using namespace std;
/*
函数作为友元
与类作为友元的区别是:
函数友元的声明在类内部 ,类作为友元可以在外部或者内部。
*/
class A {
private:
int a;
public:
A(int a) {
this->a = a;
}
friend void print_A(const A& a);
};
void print_A(const A& a) {
cout << a.a << endl;
}
int main() {
A a(20);
print_A(a);
return 0;
}
成员函数作为友元: 类的成员函数在另一个类中声明
原文地址:https://blog.csdn.net/qq_42731393/article/details/134608925
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_19667.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!