在 C++ 中,类(class)和结构体(struct)在语法上几乎是相同的,唯一的区别是默认的访问权限。在结构体中,默认的访问权限是公有的(public),而在类中,默认的访问权限是私有的(private)。默认继承方式不同。
尽管结构体在设计上更偏向于数据的聚合,而类更倾向于实现封装、继承和多态等面向对象编程的特性,但实际上,结构体也可以具有封装、继承和多态的特性。
如果使用字符数组来存储字符串,通常需要考虑字符串的结尾标志符
\0
(空字符,ASCII码为0)。在C中,字符串是以空字符\0
结尾的。因此,字符串的实际长度是字符数组长度减去1。这是因为最后一个字符用于存储空字符
\0
,用来表示字符串的结束。这个空字符告诉程序字符串的实际长度。例如:
#include
#include 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。因为字符数组的最后一个位置被空字符\0
占用。如果字符串的长度达到数组的最大长度,需要确保数组足够大以容纳字符串和结尾的空字符。
#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; \
})
#include
template
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 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;
}
默认值
#include
// 函数声明时为参数提供默认值 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; }
这两种方法都能够在一定程度上模拟函数返回多个值的效果。选择哪种方法取决于实际的需求和代码结构。
32位机通常指的是机器字长(Machine Word Length)为32位。机器字长是指在一台计算机上,CPU一次能够处理的二进制位数,也就是它的寄存器宽度。这影响了CPU能够处理的数据的大小和寻址范围。
存储字长(Storage Word Length)是指计算机在内存中一次读取或写入的二进制位数。虽然这两个概念有相似之处,但也存在区别:
机器字长(Machine Word Length):
- 影响CPU的寄存器宽度,决定CPU能够处理的单个数据块的大小。
- 直接关系到寄存器的位数,以及处理器能够执行的整数运算的范围。
- 32位机器指的是机器字长为32位,处理器一次能够处理32位的数据。
存储字长(Storage Word Length):
- 影响内存的读写宽度,即一次从内存读取或写入的数据块的大小。
- 决定了计算机在内存中的数据表示方式,以及数据的传输速率。
- 存储字长可以与机器字长相同,但也可以不同。例如,某些系统可能在内存中一次读取或写入64位的数据块,而其机器字长仍然是32位。
总的来说,机器字长主要涉及到处理器的寄存器宽度,而存储字长则涉及到内存中的数据单元大小。在32位机器上,这两者通常是相同的,但在其他架构中,可能存在不同的存储和机器字长。
不同位数的计算机体系结构可能会有不同的数据类型位数。计算机中的数据类型通常与机器字长相关,而机器字长是指计算机处理器一次能够处理的二进制位数,即寄存器宽度。
下面是一些常见的计算机体系结构和它们对应的典型的数据类型位数:
32位计算机:
int
为32位。short
为16位,long
为32位。64位计算机:
int
为64位。short
为16位,long
为64位。16位计算机:
int
为16位。具体的数据类型位数取决于编译器和计算机架构。C++标准规定了最小的数据类型位数,但允许编译器选择更大的位数。在不同的平台上,可以通过查看编译器的文档或使用sizeof
运算符来确定各种数据类型的确切位数。例如:
#include
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
和long
数据类型在当前平台上的位数。
m位的机器的指针所占空间为m位
#####7. 常量指针与指针常量 哪个在前 哪个就不可变
常量指针定义:又叫常指针(常量的指针),即这是个指向常量的指针,这个常量是指针的值(地址),而不是地址指向的值。
关键点:
指针常量定义:本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值因为是常量,所以不能被赋值。
关键点:
它是个常量;
指针所保存的地址可以改变,然而指针所指向的值却不可以改变;
指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化。
使用前要初始化
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语言)语法:
malloc
: void* malloc(size_t size);
free
: void free(void* ptr);
类型安全:
malloc
返回void*
指针,需要显式转换为实际的数据类型。初始化:
malloc
分配的内存块中的内容不进行初始化,可能包含随机值。分配大小:
new
和 delete
(C++语言)语法:
new
: Type* pointer = new Type;
或 Type* pointer = new Type[size];
delete
: delete pointer;
或 delete[] pointer;
类型安全:
new
返回正确类型的指针,不需要显式转换。初始化:
new
分配的内存块中的内容会调用对象的构造函数,保证对象处于有效状态。分配大小:
new
会自动计算大小。new
可以用于分配数组,delete[]
用于释放整个数组。异常处理:
new
在分配失败时抛出 std::bad_alloc
异常,需要使用 try-catch
进行处理。malloc
在分配失败时返回 NULL
,需要手动检查。总体而言,new
和 delete
提供了更多的功能和类型安全性,适用于C++中的对象。而 malloc
和 free
是C语言中的函数,更低级,用于简单的内存分配和释放。在C++中,推荐使用 new
和 delete
来管理动态内存。
先简述共同点,再从类型安全,初始化,分配大小,以及对象的创建和销毁的角度说明
左值(Lvalue)和右值(Rvalue)是C++中的基本概念,它们与表达式和对象的值的生命周期相关。以下是一个简洁而全面的回答:
左值(Lvalue):
&
获取其地址。右值(Rvalue):
&
取地址会导致编译错误。回答示例:
左值和右值是C++中用于描述表达式的术语。左值是具有标识符的表达式,具有持久性,可以取地址。典型的例子包括变量和通过引用访问的对象。右值是临时性的,通常在表达式求值后立即被销毁。它通常出现在赋值语句的右侧,是表达式的计算结果。***左值和右值的区别在于它们的生命周期和是否可寻址。***在C++11及更高版本中,右值引用(Rvalue Reference)的引入进一步强调了右值的重要性,例如移动语义的实现。
关于右值引用,可以补充说明在C++11之后,引入了右值引用这个新的类型,可以通过 &&
定义。右值引用允许我们更有效地处理右值,如实现移动语义,提高性能。
#####10. 四大类型转换
在C++中,有四种基本的类型转换,通常被称为 “四大转换”,它们分别是:静态转换(static_cast
)、动态转换(dynamic_cast
)、常量转换(const_cast
)、重新解释转换(reinterpret_cast
)。这些转换提供了不同的功能,涉及到不同的情景和要求。以下是一个简洁的回答:
static_cast
):
int num = static_cast<int>(3.14);
Base* basePtr = static_cast<Base*>(derivedPtr);
dynamic_cast
):
nullptr
或引发 std::bad_cast
异常。Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr != nullptr) {
// 安全地使用 derivedPtr
}
const_cast
):
const
属性或 volatile
属性。const
属性,这样的行为是未定义的。const int value = 42;
int* mutableValue = const_cast<int*>(&value);
reinterpret_cast
):
int intValue = 10;
double* doublePtr = reinterpret_cast<double*>(&intValue);
在回答时,可以简要介绍每一种转换的主要用途和注意事项。强调在使用这些转换时需要慎重,最好在确保安全性和可维护性的前提下使用。
作用:**函数名可以相同,提高复用性
函数重载满足条件:
注意: 函数的返回值不可以作为函数重载的条件
在C++中,面向对象编程(OOP)的三大特性分别是封装(Encapsulation)、继承(Inheritance)、和多态(Polymorphism)。
封装(Encapsulation):
public
、private
、protected
),控制对类的成员的访问权限,提高了安全性。继承(Inheritance):
多态(Polymorphism):
总体而言,面向对象编程的这三大特性有助于提高代码的可维护性、可读性、重用性和灵活性。它们提供了一种结构化的方法,使得程序员能够更好地组织和设计代码,同时通过封装、继承和多态实现抽象、简化和模块化。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:**类名(){}
构造函数,没有返回值也不写void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数
调用时机: 析构函数会在对象生命周期结束时被调用,这可能是在对象离开其作用域时,或者通过 delete
运算符显式删除动态分配的对象时。
只调用一次: 每个对象的析构函数只会被调用一次。当对象的生命周期结束时,析构函数会被自动调用,清理资源并执行必要的操作。如果对象是动态分配的(使用 new
创建),则在调用 delete
时会触发析构函数的调用。
析构函数语法:**~类名(){}
构造函数,没有返回值也不写void
函数名称在类名加~
构造函数不可以有参数,因此不可以发生重载
在对象生命周期结束时,会自动调用析构函数。
两种分类方式:
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
深浅拷贝
浅拷贝:简单的赋值拷贝操作
***深拷贝:在堆区重新申请空间,进行拷贝操作 ***
为了避免浅拷贝时,堆内存数据重复释放
深拷贝(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;
}
};
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,称为静态成员
静态成员分为:
成员变量与成员方法分开存储
好处:
1.属性与行为相分离:提高可维护性 增强可读性
2.节省内存:属性表明对象的状态,而成员函数是所有对象共享的。
3.生命周期不同
4.权限访问控制。
友元是一种在C++中的特殊机制,它允许一个函数或者类访问另一个类的私有成员。友元可以提供对类的某些成员的访问权限,打破了类的封装性。友元可以是一个函数,一个类,或者一个类中的成员函数。然而,要谨慎使用友元,因为过度使用可能导致代码的复杂性增加,破坏了封装性,使代码变得难以理解和维护。因此,在设计中应该限制友元的使用,确保它只在必要的情况下被使用。"
#####18.运算符重载
运算符重载是C++中的一项特性,它允许程序员重新定义或者扩展基本的运算符,使其能够用于自定义数据类型。基本的运算符通常仅对内置数据类型进行操作,但运算符重载使得我们可以定义类的成员函数或全局函数,以实现对自定义数据类型的运算。通过运算符重载,我们能够提高代码的可读性和表达性,使得自定义类型的对象可以像内置类型一样进行直观的运算操作。这种能力允许我们在自定义类型上使用类似于 +
、-
、*
等运算符,从而使代码更加简洁、优雅,并且更符合直觉。
#####19.子类继承父类的方式
子类继承父类有三种方式,公有继承,受保护的继承,私有继承。无论哪种继承方式,子类均可以访问到父类的非私有成员。但子类的继承的成员的可见性会发生变化。如果是共有继承,那么可见性与父类一致,如果是受保护的继承,则除了父类的私有成员,其他的可见性都变成了受保护,如果是私有继承,那么可见性均变为私有。
1.受保护权限和所有权限的区别主要在于派生类继承对父类成员的可见性的区别,而类外的可见性没有区别
2.继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
#####20.虚继承
虚继承是C++中用于解决菱形继承问题(Diamond Problem)的一种技术。当一个类被虚继承时,它的派生类只会继承一份基类的实例,而不是多份。这样可以防止由于多次继承同一基类而导致的二义性和资源浪费。
#####21.多态的概念
多态是面向对象编程的一项重要特性,它允许通过父类指针或引用来访问派生类对象,通过同一个父类接口可以调用不同子类对象的属性或方法。多态的实现条件主要有三个:首先,父类必须有虚函数;其次,子类必须重写这个虚函数;最后,我们通过父类指针或引用指向子类对象。
多态的核心在于动态绑定,这意味着在运行时才确定调用哪个函数。当我们通过父类指针调用虚函数时,程序会在运行时根据指针所指的具体对象类型来动态地确定调用的是哪个函数。这样,同一份代码可以处理不同类型的对象,从而提高了代码的灵活性和可扩展性
在包含虚函数的类中,编译器在对象的内存布局中插入了一个称为虚函数指针(vptr)的指针。这个指针指向该类的虚函数表,虚函数表是一个数组,包含了该类及其基类的虚函数地址。对于每一个具体的对象,虚函数指针都会被初始化为指向其所属类的虚函数表。
在运行时,当我们通过基类指针或引用调用虚函数时,程序会使用虚函数指针来访问虚函数表。动态绑定的过程发生在运行时,通过查询虚函数表确定到底调用哪个函数。这意味着,即使是通过基类指针或引用调用虚函数,实际调用的是与对象的实际类型相对应的函数,而不是基类中的函数。
***总的来说,虚函数指针和虚函数表提供了一种在运行时动态地确定调用哪个虚函数的机制,实现了多态性 ***
模板包括函数模板与类模板 可以将类型参数化,这允许我们编写出与数据类型无关的代码,增强了代码复用与开发效率 同时模板也是类型安全的
#####24. 友元
友元作为类 注意要引用一个类,那个对象要先声明
#include
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
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;
}
成员函数作为友元: 类的成员函数在另一个类中声明