基本数据类型:C++提供了多种基本数据类型,如int、float、double、char等。这些数据类型用于存储不同类型的数据。
用户定义的数据类型:除了基本数据类型,用户还可以定义自己的数据类型,如结构体(struct)、类(class)等。
数组:数组是一种可以存储多个同类型数据的数据结构。例如,一个整数数组可以存储多个整数。
字符串:C++中,字符串通常表示为字符数组,也可以使用标准库中的std::string类来处理。
数组是一种数据结构,它包含一组有序的元素,每个元素可以通过其索引来访问。在数组中,每个元素都有一个唯一的索引,该索引用于标识该元素在数组中的位置。数组的大小是固定的,一旦创建,其大小就不能更改。
在许多编程语言中,如C++、Java和Python等,都有数组这种数据结构。数组可以是一维或多维的,其中一维数组是最简单的形式,而多维数组可以用来表示矩阵、三维空间中的点等复杂的数据结构。
定义:指针是一个变量,其值为另一个变量的地址。通过指针,我们可以间接访问和修改变量的值。
指针的声明:例如,int *ptr;
声明了一个指向整数的指针ptr。
举一个简单的例子来说明指针的概念:
假设你有一本电话簿,上面列出了许多朋友的电话号码。每个电话号码占据了一个页码。如果你想给朋友打电话,你首先需要找到相应的页码,然后翻到那一页找到电话号码。在这个场景中,页码就是指针,它指向了电话号码的具体位置。
现在,如果我们想给不同的朋友打电话,我们可能需要多次翻动电话簿。这种频繁的翻动很麻烦,但如果我们可以直接找到所有朋友的电话号码的存放位置,一次性记录下来,那么我们就可以快速地找到任何一个朋友的电话号码。这个存放所有电话号码的位置就是指针数组。
通过这个例子,可以将指针理解为一个指南针,它指向了某个数据的位置,这样我们就可以快速地找到并使用这些数据。
在C++中,我们用星号(*
)前缀来表示指针。例如,如果你声明了一个int类型的变量和一个指向int的指针,你可以这样做:
int num = 10; // 定义一个int变量
int *ptr; // 定义一个指向int的指针
接下来,让指针指向这个变量:
ptr = # // ptr现在存储了num的地址
&
符号用于获取变量的地址。当我们对一个变量使用&
操作符时,我们得到的是该变量的内存地址,而不是它的值。
现在,你可以通过指针来访问和修改变量的值:
*ptr = 20; // 通过指针修改num的值
以上就是指针的基本概念。
再举一个简单的示例,演示指针和&
符号的使用:
#include
int main() {
int num = 10;
int *ptr = # // ptr指向num的地址
std::cout << "num的值:" << num << std::endl;
std::cout << "num的地址:" << &num << std::endl;
std::cout << "ptr指向的地址:" << ptr << std::endl;
std::cout << "ptr指向的值:" << *ptr << std::endl; // 使用*来解引用指针,获取指针所指向的值
return 0;
}
输出的结果为:
num的值:10
num的地址:0x61fe14
ptr指向的地址:0x61fe14
ptr指向的值:10
在这个示例中,我们定义了一个整数变量num,并声明了一个指向整数的指针ptr。通过使用&符号,我们将num的地址赋给ptr。然后,我们使用*符号来解引用指针,获取指针所指向的值,即num的值。
指针的算术操作包括指针的加法、减法、算术赋值和比较操作。
int *
类型,那么ptr +1
会将指针向前移动4个字节(在32位系统上),因为int类型通常占用4个字节。ptr2 - ptr1
会计算两个指针之间的差值,结果是一个整数。ptr += 5
等价于ptr =ptr + 5
。请注意,当对指针进行算术操作时,必须确保指针指向有效的内存地址,否则可能导致未定义的行为或程序崩溃。此外,不同编译器和平台可能对指针算术有不同的行为,因此在实际应用中应谨慎处理。
当一个指针被声明了,但没有被赋值时,它的值是未定义的,这就是所谓的空指针。空指针不同于NULL指针,NULL指针的值是0。
#include
int main() {
int *ptr; // 声明一个整型指针
if (ptr == nullptr) {
std::cout << "ptr is null" << std::endl;
} else {
std::cout << "ptr is not null" << std::endl;
}
return 0;
}
在这个例子中,我们声明了一个整型指针ptr,但没有给它赋值。然后我们检查ptr是否为空。
因为ptr是未初始化的,所以它的值是未定义的,可能是任何值,包括0。
因此,上面的代码可能会输出"ptr is null",也可能会输出"ptr is not null",这取决于ptr的未定义值。
为了避免这种不确定性,最好在使用指针之前将其初始化为NULL
或nullptr
。这样,你可以明确地知道指针是否为空,避免出现未定义的行为。
野指针是指一个指针被删除或释放后,仍然保留着原来的内存地址,但由于该内存地址已经不属于任何有效的变量或对象,因此被称为“野指针”。
野指针是非常危险的,因为它们可能会导致程序崩溃、数据损坏或其他未定义的行为。当一个指针指向的内存被释放后,该指针就变成了野指针,如果仍然试图通过这个指针访问内存,就会产生不可预知的结果。
为了避免野指针的问题,应该遵循以下规则:
NULL
或nullptr
,以确保它不指向任何有效的内存地址。NULL
或nullptr
,以便清楚地表明它不再指向任何有效的内存地址。unique_ptr
、shared_ptr
和weak_ptr
。使用智能指针的示例:
#include
#include
/*具体来说, 头文件提供了几种智能指针类型:
std::unique_ptr: 独占所有权的智能指针,确保所指向的对象在任何时刻都只有一个unique_ptr拥有它。当unique_ptr被销毁时,它所指向的对象也会被自动删除。
std::shared_ptr: 允许多个指针共享同一个对象的所有权。当最后一个指向对象的shared_ptr被销毁时,对象才会被删除。
std::weak_ptr: 与shared_ptr一起使用,用于解决shared_ptr之间的循环引用问题。
在这段代码中,std::make_unique 和 std::make_shared 是创建智能指针的便捷函数,它们也定义在 头文件中。*/
//定义一个名为MyClass的类。
class MyClass {
public:
//类的访问修饰符,表示以下的内容是公共的,可以在类的外部被访问
MyClass(int value) : value_(value) {}
//MyClass的构造函数。它接受一个整数参数value,并使用这个值初始化成员变量value_。
void printValue() { std::cout << value_ << std::endl; }
//这是一个公共成员函数,名为printValue。它打印成员变量value_的值到标准输出。
private:
//修饰符,表示以下的内容是私有的,只能在类的内部被访问
int value_;
};
int main() {
// 使用std::make_unique创建unique_ptr
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(10);
ptr1->printValue(); // 输出:10
// 使用std::make_shared创建shared_ptr
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>(20);
ptr2->printValue(); // 输出:20
return 0;
}
在这个示例中,我们使用了两种智能指针:std::unique_ptr
和std::shared_ptr
。
std::unique_ptr
是一种独占所有权的智能指针,它确保了所指向的对象在任何时刻都只有一个unique_ptr
拥有它。
当unique_ptr
被销毁时(例如,离开其作用域),它所指向的对象也会被自动删除。而std::shared_ptr
则允许多个指针共享同一个对象的所有权。当最后一个指向对象的shared_ptr
被销毁时,对象才会被删除。
在C++中,指针的类型匹配非常重要,因为它决定了指针所指向的数据类型以及如何通过指针访问这些数据。例如,一个int *类型的指针不能用来存储double类型的地址。
以下是关于指针类型匹配的一些要点:
int *ptr
; 定义了一个指向整数的指针。 int *ptr = static_cast<int *>(ptr2);
int *int_ptr = (int *) ptr;
用new分配的内存必须使用delete释放,使用malloc分配的内存必须使用free释放。
忘记释放内存会导致内存泄漏。
在C++中,new操作符用于在堆上动态分配内存。当你使用new为一个对象分配内存时,你实际上是在请求内存分配器从堆上为你取出一块足够大的内存。当你不再需要这块内存时,应该使用delete操作符来释放它。如果你忘记使用delete,就会发生内存泄漏,因为那块内存将永远不会被回收,从而导致程序占用的内存逐渐增加。
在C语言中(虽然C++也支持malloc和free),malloc和free是用于动态内存分配和释放的函数。与C++的new和delete类似,当使用malloc为一个数据结构分配内存时,应该使用free来释放它。忘记使用free同样会导致内存泄漏。
忘记释放内存会导致内存泄漏:——这是上述两点的一个总结。
如果你使用new、malloc或其他类似的函数为数据结构分配了动态内存,但之后没有释放它,那么这块内存将永远不会被回收。
这不仅会导致程序使用的内存量逐渐增加,而且如果程序长时间运行或频繁进行动态内存分配,可能会导致可用内存耗尽,从而引发严重的问题。
为了避免这些问题,通常建议程序员使用智能指针(如C++11引入的std::unique_ptr
和std::shared_ptr
)来管理动态内存。智能指针可以自动管理对象的生命周期,确保在指针超出作用域时内存被正确释放,从而减少因忘记释放内存而导致的错误。
#include
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor called." << std::endl; }
~MyClass() { std::cout << "MyClass destructor called." << std::endl; }
};
int main() {
// 使用new在堆上动态分配内存
MyClass* ptr = new MyClass();
// 在此处执行其他操作...
// 使用delete释放内存
delete ptr;
return 0;
}
在C++中,数组名本质上是指向数组第一个元素的常量指针。这句话可以这样理解:
在C和C++中,数组名实际上是一个指向数组第一个元素的常量指针(或者说是地址)。当我们定义一个数组,如int arr[10];
,这个数组的名字(arr)
就代表了数组的第一个元素的地址。
例如,如果我们想访问数组的第一个元素,我们可以使用数组名直接访问:
int arr[10];
arr[0] = 5; // 这等价于 *(arr + 0) = 5;
在上面的代码中,arr
实际上是一个指向数组第一个元素的指针。当我们写arr[0]
,这等价于*(arr + 0)
。在C和C++中,数组名会自动进行这个指针算术操作,所以我们通常直接使用数组名来访问元素。
需要注意的是,数组名是一个常量指针,所以你不能改变它所指向的地址。也就是说,你不能让数组名指向其他地方。例如,以下的代码是错误的:
int arr[10];
int *p = arr; // 错误!你不能改变数组名的指向。
总结一下,数组名本质上是指向数组第一个元素的常量指针。它为我们提供了一种方便的方式来访问数组元素。
注意函数参数:当传递数组到函数时,实际上是传递了数组的首地址。如果你想在函数内部修改数组的内容,你需要传递指向数组的指针。
const
关键字:当你声明一个指针为const时,意味着你不能通过这个指针修改所指向的值。例如,const int *ptr;
表示你不能通过ptr来修改变量的值。