四时宝库

程序员的知识宝库

划重点,C++入门指南(下)

(接中篇)

数组与指针

数组

数组是 C++ 中一种非常重要的数据结构,它允许我们将多个相同类型的元素存储在连续的内存空间中,就像一排整齐排列的小盒子,每个小盒子都可以存放一个数据,并且这些小盒子在内存中是紧密相连的。通过数组,我们可以方便地对一组相关数据进行管理和操作,大大提高编程的效率和灵活性。

一维数组

一维数组是最基本的数组形式,它的定义方式为:数据类型 数组名[数组大小]; 。例如,定义一个包含 5 个整数的一维数组:

int numbers[5];

这里,int 是数组中元素的数据类型,numbers 是数组名,[5] 表示数组的大小,即该数组可以容纳 5 个整数元素。在定义数组时,数组大小必须是一个常量表达式,不能是变量,这是因为数组在内存中所占的空间大小在编译时就需要确定下来。

数组的初始化可以在定义时进行,有多种初始化方式。例如,使用花括号进行初始化:

int numbers[5] = {10, 20, 30, 40, 50};

这样就将数组 numbers 的 5 个元素分别初始化为 10、20、30、40 和 50。如果初始化时提供的元素个数少于数组大小,剩余的元素会自动初始化为 0(对于整型数组来说)。例如:

int numbers[5] = {10, 20};

此时,numbers[0] 为 10,numbers[1] 为 20,numbers[2]numbers[3]numbers[4] 都为 0。

还可以省略数组大小,让编译器根据初始化列表中的元素个数自动推断数组大小:

int numbers[] = {10, 20, 30, 40, 50};

在这种情况下,编译器会自动确定数组 numbers 的大小为 5。

访问一维数组中的元素是通过下标(索引)来进行的,下标从 0 开始。例如,要访问数组 numbers 的第 3 个元素(注意,下标从 0 开始,所以第 3 个元素的下标是 2),可以这样写:

int thirdElement = numbers[2];

数组在使用下标访问元素时,一定要注意不要越界,即访问的下标不能小于 0,也不能大于或等于数组的大小。否则,会导致未定义行为,可能会使程序崩溃或产生不可预测的结果。例如:

int numbers[5] = {10, 20, 30, 40, 50};

int illegalElement = numbers[5]; // 错误,数组越界,numbers的有效下标范围是0到4

在实际应用中,我们经常会使用循环来遍历数组,对数组中的每个元素进行操作。例如,计算数组中所有元素的和:

int numbers[5] = {10, 20, 30, 40, 50};

int sum = 0;

for (int i = 0; i < 5; i++) {

   sum += numbers[i];

}

在这个例子中,通过 for 循环,从下标 0 开始,依次访问数组 numbers 的每个元素,并将其累加到 sum 变量中,最终得到数组所有元素的和。

二维数组

二维数组可以看作是数组的数组,它在内存中也是连续存储的,不过可以将其想象成一个表格或矩阵,有行和列的概念。二维数组的定义方式为:数据类型 数组名[行数][列数]; 。例如,定义一个 3 行 4 列的二维数组:

int matrix[3][4];

这里,int 是数组元素的数据类型,matrix 是数组名,[3] 表示行数,[4] 表示列数,该二维数组可以存储 3 行 4 列共 12 个整数元素。

二维数组的初始化同样可以在定义时进行,有多种方式。例如,使用花括号进行分行初始化:

int matrix[3][4] = {
  {1, 2, 3, 4},
  {5, 6, 7, 8},
  {9, 10, 11, 12}
};

这种方式非常直观,每一行的元素用一对花括号括起来,清晰地表示了二维数组的结构。也可以进行部分初始化,未初始化的元素会自动初始化为 0:

int matrix[3][4] = {
{1, 2},
{5}
};

此时,matrix[0][0] 为 1,matrix[0][1] 为 2,matrix[1][0] 为 5,其余元素都为 0。

访问二维数组中的元素需要使用两个下标,第一个下标表示行,第二个下标表示列,下标同样从 0 开始。例如,要访问 matrix 数组中第 2 行第 3 列的元素(注意,行下标为 1,列下标为 2),可以这样写:

int element = matrix[1][2];

在实际应用中,通常会使用嵌套循环来遍历二维数组。例如,打印二维数组的所有元素:

int matrix[3][4] = {

   {1, 2, 3, 4},

   {5, 6, 7, 8},

   {9, 10, 11, 12}

};

for (int i = 0; i < 3; i++) {

   for (int j = 0; j < 4; j++) {

       cout << matrix[i][j] << " ";

   }

   cout << endl;

}

在这个例子中,外层 for 循环控制行,内层 for 循环控制列,通过嵌套循环,依次访问二维数组 matrix 的每个元素,并将其打印出来,从而展示出二维数组的完整内容。

指针(最难学的,多写编码试试)

指针是 C++ 中一个强大而又灵活的特性,它为我们提供了直接访问内存的能力,就像一把能打开内存空间大门的钥匙。通过指针,我们可以更加高效地管理和操作内存,实现一些复杂的数据结构和算法。然而,指针的使用也需要格外小心,因为一旦使用不当,就可能会导致程序出现严重的错误,如内存泄漏、悬空指针等问题。

指针的定义与初始化

指针是一种特殊的变量,它存储的是另一个变量的内存地址,而不是变量的值本身。定义指针变量时,需要指定指针所指向的数据类型,其语法格式为:数据类型 *指针变量名; 。例如,定义一个指向整型变量的指针:

int *ptr;

这里,int 表示指针 ptr 所指向的变量是整型,* 是指针声明符,用于表明 ptr 是一个指针变量。

在使用指针之前,通常需要对其进行初始化,使其指向一个有效的内存地址。例如,先定义一个整型变量,然后让指针指向它:

int num = 10;

int *ptr = #

在这两行代码中,首先定义了一个整型变量 num 并初始化为 10,然后定义了一个指针 ptr,并使用取地址运算符 & 获取 num 的地址,将其赋值给 ptr,此时 ptr 就指向了 num 变量。

也可以在定义指针时直接初始化:

int *ptr = new int(20);

这里使用 new 运算符在堆内存中分配了一个整型变量,并将其初始化为 20,然后将这个新分配的变量的地址赋值给指针 ptr。需要注意的是,使用 new 分配内存后,在不再使用这块内存时,一定要使用 delete 运算符来释放内存,以避免内存泄漏。例如:

delete ptr;

指针的运算

指针的运算主要包括加法、减法和比较运算,这些运算都是基于指针所指向的内存地址进行的,并且与指针所指向的数据类型的大小密切相关。

指针加法:当对指针进行加法运算时,并不是简单地将指针的值加上一个整数,而是根据指针所指向的数据类型的大小,将指针移动相应的字节数。例如,对于一个指向整型的指针,每个整型通常占用 4 个字节(在 32 位系统中),如果将指针加 1,实际上是将指针的地址值增加 4 个字节,使其指向下一个整型元素的地址。例如:

int arr[5] = {10, 20, 30, 40, 50};

int *ptr = arr; // 数组名本身就是数组首元素的地址,所以ptr指向arr[0]

ptr++; // ptr现在指向arr[1],地址增加了4个字节(假设int占4字节)

在这个例子中,ptr 最初指向 arr[0],执行 ptr++ 后,ptr 指向下一个整型元素 arr[1],其地址增加了 4 个字节。

指针减法:指针减法与加法类似,是将指针的地址值减去相应的字节数。例如,两个指针相减,可以得到它们之间相差的元素个数(前提是这两个指针指向同一个数组)。例如:

int arr[5] = {10, 20, 30, 40, 50};

int *ptr1 = &arr[0];

int *ptr2 = &arr[3];

int diff = ptr2 - ptr1; // diff的值为3,表示ptr2和ptr1之间相差3个元素

在这个例子中,ptr1 指向 arr[0]ptr2 指向 arr[3]ptr2 - ptr1 的结果是 3,即它们之间相差 3 个整型元素。

指针比较:指针可以进行比较运算,如 ==(等于)、!=(不等于)、<(小于)、>(大于)等。指针比较是基于它们所指向的内存地址进行的。例如,比较两个指针是否指向同一个地址:

int num1 = 10;

int num2 = 20;

int *ptr1 = &num1;

int *ptr2 = &num2;

if (ptr1!= ptr2) {

   cout << "ptr1和ptr2指向不同的地址" << endl;

}

在这个例子中,ptr1 指向 num1ptr2 指向 num2,由于它们指向不同的变量,所以地址不同,ptr1!= ptr2 的结果为 true

指针与数组的关系

指针和数组之间有着密切的联系,在很多情况下,数组名可以看作是一个常量指针,它指向数组的第一个元素。例如:

int arr[5] = {10, 20, 30, 40, 50};

int *ptr = arr; // 等价于 int *ptr = &arr[0];

在这个例子中,arr 是数组名,同时也可以看作是一个指向 arr[0] 的常量指针,所以可以将 arr 直接赋值给指针 ptr,此时 ptr 也指向了 arr[0]

通过指针可以像使用数组下标一样访问数组元素。例如:

int arr[5] = {10, 20, 30, 40, 50};

int *ptr = arr;

cout << *(ptr + 2) << endl; // 输出30,等价于cout << arr[2] << endl;

这里,*(ptr + 2) 表示先将指针 ptr 移动 2 个元素的位置(即指向 arr[2]),然后解引用指针,获取指针所指向的元素的值,其效果与 arr[2] 是一样的。

实际上,arr[i] 这种数组下标访问方式在底层就是通过指针运算来实现的,它等价于 *(arr + i) 。例如:

int arr[5] = {10, 20, 30, 40, 50};

for (int i = 0; i < 5; i++) {

   cout << arr[i] << " "; // 等价于 cout << *(arr + i) << " ";

}

在这个循环中,arr[i]*(arr + i) 都可以正确地访问数组 arr 的每个元素,并将其输出。

指针与内存管理

在 C++ 中,内存管理是一项非常重要的任务,而指针在内存管理中扮演着关键的角色。正确地使用指针进行内存管理,可以确保程序高效、稳定地运行;反之,如果内存管理不当,就可能会导致内存泄漏、悬空指针等严重问题,影响程序的性能和稳定性。

动态内存分配与释放

在 C++ 中,我们可以使用 new 运算符在堆内存中动态分配内存,使用 delete 运算符来释放动态分配的内存。动态内存分配为我们提供了更大的灵活性,使我们能够在程序运行时根据实际需要分配内存空间,而不是在编译时就确定内存的大小。

使用 new 运算符分配内存的基本语法如下:

数据类型 *指针变量 = new 数据类型;

例如,分配一个整型变量的内存空间:

int *ptr = new int;

*ptr = 10; // 给分配的内存赋值

这里,new int 表示在堆内存中分配一个整型大小的内存空间,并返回该内存空间的地址,将其赋值给指针 ptr。然后可以通过指针 ptr 来访问和操作这块内存,如给它赋值为 10。

如果要分配一个数组的内存空间,可以使用以下语法:

数据类型 *指针变量 = new 数据类型[数组大小];

例如,分配一个包含 5 个整型元素的数组的内存空间:

int *arr = new int[5];

for (int i = 0; i < 5; i++) {

   arr[i] = i * 2; // 给数组元素赋值

}

在这个例子中,new int[5] 在堆内存中分配了一块连续的内存空间,足以容纳 5 个整型元素,并返回这块内存空间的首地址,赋值给指针 arr。然后通过循环给数组的每个元素赋值。

当我们不再需要动态分配的内存时,必须使用 delete 运算符来释放它,以避免内存泄漏。释放单个变量的内存使用 delete,释放数组的内存使用 delete[]。例如:

int *ptr = new int;

*ptr = 10;

delete ptr; // 释放单个变量的内存

int *arr = new int[5];

for (int i = 0; i < 5; i++) {

   arr[i] = i * 2;

}

delete[] arr; // 释放数组的内存

在这两个例子中,delete ptr 释放了之前使用 new 分配的单个整型变量的内存,delete[] arr 释放了使用 new[] 分配的包含 5 个整型元素的数组的内存。如果忘记释放动态分配的内存,随着程序的运行,堆内存会被不断占用,最终可能导致内存耗尽,程序崩溃。

避免内存泄漏和悬空指针

内存泄漏是指动态分配的内存没有被正确释放,导致这些内存无法被程序再次使用,从而浪费了系统资源。悬空指针则是指指针指向的内存已经被释放,但指针仍然保留着这个无效的地址,当通过悬空指针访问内存时,会导致未定义行为,可能会使程序崩溃或产生不可预测的结果。

为了避免内存泄漏,一定要确保在不再需要动态分配的内存时,及时使用 deletedelete[] 进行释放。例如,在一个函数中分配了内存,在函数结束前一定要释放:

void someFunction() {
   int *ptr = new int;

   *ptr = 10;

   // 其他操作

   delete ptr; // 释放内存,避免内存泄漏

}

在这个例子中,someFunction 函数中分配了一个整型变量的内存,在函数结束前使用 delete 释放了这块内存,从而避免了内存泄漏。

为了避免悬空指针,在释放内存后,将指针赋值为 nullptr(在 C++11 及以后版本中)或 NULL(在 C++11 之前版本中)。例如:

int *ptr = new int;

*ptr = 10;

delete ptr;

ptr = nullptr; // 将指针赋值为nullptr,避免悬空指针

在这个例子中,释放内存后将 ptr 赋值为 nullptr,这样 ptr 就不再指向无效的内存地址,避免了悬空指针的问题。在使用指针之前,最好先检查指针是否为 nullptr,以确保指针指向的是有效的内存:

int *ptr = nullptr;

if (ptr!= nullptr) {

   // 使用ptr

} else {

   // 处理指针为nullptr的情况

}

在这个代码片段中,通过检查 ptr 是否为 nullptr,可以避免在

结构体与类(初步介绍)

结构体

在 C++ 编程中,结构体是一种非常实用的用户自定义数据类型,它允许我们将不同类型的数据组合在一起,形成一个有机的整体。这就好比我们日常生活中的一个工具箱,里面可以装各种不同的工具,如锤子、螺丝刀、扳手等,这些工具虽然类型不同,但都被放在同一个工具箱里,方便我们使用。结构体在组织相关数据方面有着重要的作用,它能够将一组相关的数据紧密地联系在一起,使程序的结构更加清晰、逻辑更加严谨。

结构体的定义使用struct关键字,其基本语法格式如下:

struct 结构体名 {

   数据类型 成员1;

   数据类型 成员2;

   // 可以有更多的成员

};

例如,我们要定义一个表示学生信息的结构体,其中包含学生的姓名、年龄和成绩,就可以这样写:

struct Student {

   std::string name;

   int age;

   double score;

};

在这个例子中,Student是结构体名,nameagescore是结构体的成员,分别表示学生的姓名(std::string类型)、年龄(int类型)和成绩(double类型)。

定义好结构体后,我们就可以创建结构体变量,并访问其成员。创建结构体变量的方式和定义普通变量类似,例如:

Student stu1;

这里创建了一个名为stu1Student类型的结构体变量。要访问结构体变量的成员,可以使用点运算符(.),例如:

stu1.name = "张三";

stu1.age = 20;

stu1.score = 85.5;

通过点运算符,我们可以分别给stu1nameagescore成员赋值。

结构体变量的初始化也有多种方式,一种常见的方式是在定义变量时使用初始化列表,例如:

Student stu2 = {"李四", 21, 90.0};

这样就同时定义并初始化了stu2结构体变量,将其name初始化为 "李四",age初始化为 21,score初始化为 90.0。

结构体在实际应用中非常广泛,比如在一个学生管理系统中,我们可以使用结构体来存储每个学生的信息,然后通过数组或链表等数据结构来管理这些学生信息。又比如在一个游戏开发中,我们可以用结构体来表示游戏中的角色,包含角色的名称、生命值、攻击力等属性,方便对角色进行操作和管理。

类的初步认识

类是 C++ 中面向对象编程的核心概念,它是一种用户自定义的数据类型,将数据成员和成员函数封装在一起,形成一个具有特定功能和行为的抽象实体。可以说,类就像是一个具有特定功能的 “机器”,数据成员是这个机器的各种零件,而成员函数则是这个机器的操作方法,通过这些操作方法,可以对数据成员进行各种操作和处理。

从概念上讲,类是对现实世界中一类具有共同属性和行为的事物的抽象。例如,在现实生活中,汽车是一类具有共同特征的事物,它们都有品牌、颜色、速度等属性,并且都具有启动、加速、刹车等行为。在 C++ 中,我们可以定义一个Car类来表示汽车,其中品牌、颜色、速度等属性可以作为类的数据成员,而启动、加速、刹车等行为可以作为类的成员函数。

类的定义使用class关键字,其基本语法格式如下:

class 类名 {

public:

   // 公有成员函数和数据成员

   数据类型 成员1;

   返回值类型 成员函数1(参数列表) {

       // 函数体

   }

private:

   // 私有成员函数和数据成员,外部不能直接访问

   数据类型 成员2;

   返回值类型 成员函数2(参数列表) {

       // 函数体

   }

};

在类的定义中,publicprivate是访问修饰符,用于控制类成员的访问权限。public修饰的成员可以在类的外部直接访问,而private修饰的成员只能在类的内部访问,这体现了类的封装性,将类的内部实现细节隐藏起来,只对外提供公共的接口,提高了代码的安全性和可维护性。

例如,我们定义一个简单的Circle类来表示圆,其中包含圆的半径作为数据成员,以及计算圆面积和周长的成员函数:

class Circle {

public:

   double radius; // 半径,公有数据成员

   // 计算圆面积的成员函数

   double calculateArea() {

       return 3.14 * radius * radius;

   }

   // 计算圆周长的成员函数

   double calculateCircumference() {

       return 2 * 3.14 * radius;

   }

};

在这个例子中,radiusCircle类的公有数据成员,calculateAreacalculateCircumference是公有成员函数。我们可以创建Circle类的对象,并访问其公有成员:

Circle myCircle;

myCircle.radius = 5.0;

double area = myCircle.calculateArea();

double circumference = myCircle.calculateCircumference();

这里创建了一个myCircle对象,设置其半径为 5.0,然后调用calculateAreacalculateCircumference成员函数分别计算圆的面积和周长。

类的引入为我们编写大型、复杂的程序提供了更好的组织和管理方式,它是面向对象编程的基础,通过类,我们可以实现代码的复用、继承和多态等强大的特性,为后续深入学习面向对象编程打下坚实的基础。

总结与展望

恭喜你,已经完成了 C++ 编程的入门之旅!通过这篇文章,你已经了解了 C++ 的基本概念和语法,包括变量与数据类型、运算符与表达式、流程控制结构、函数、数组与指针、结构体与类等关键知识点。这些知识是你踏入 C++ 编程世界的基石,希望你能牢牢掌握。

编程是一门实践性很强的技能,理论知识固然重要,但只有通过大量的实践,才能真正掌握 C++ 编程。在学习过程中,不要害怕犯错,每一次错误都是一次成长的机会。遇到问题时,多查阅资料,多思考,多尝试不同的解决方案。

C++ 编程的世界丰富多彩,还有许多高级的知识等待你去探索。例如,面向对象编程是 C++ 的核心特性之一,它通过封装、继承和多态等机制,让你能够构建更加复杂、可维护的程序。模板是 C++ 强大的泛型编程工具,它允许你编写通用的代码,适用于不同的数据类型。C++ 标准库更是提供了丰富的功能,如容器(如 vector、map 等)、算法(如排序、查找等),能够大大提高你的开发效率。

希望你能保持对 C++ 编程的热情,不断学习,不断进步。如果你在学习过程中有任何问题或心得,欢迎在评论区留言分享,让我们一起交流,共同成长!

完结!!!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接