c++笔记(8) 指针及动态内存管理
指针:指针变量,可以用用来引用数组,对象,任何变量的地址。指针中保存的是地址,用*(解引运算符)来访问内存中的数据
指针的使用:
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 using namespace std; int main(int argc, char *argv[]) { int count = 5; int* p; p = &count; cout << "The vauel of count is " << *p << endl; cout << "The address of count is " << p << endl; return 0; }
与局部变量一样,如果不为局部指针赋值,其内容是任意的,可以将一个指针值赋值为0,这是一个特殊的指针值,表示指针未指向任何变量。因此,应该始终对指针保持初始化。以避免错误。
2.用typedef定义同义类型
typedef int* intPointer
typedef并不是创造新的数据类型,而是给已有的数据类型起一个别名
3.常量指针
常量指针指向一个不变的内存位置,但是内存中的数据是可以改变的
double radius = 5;
double* const p = &radius; //常量指针
const double *p = &radius; // 指针指向的数据(radius)是常量
const double* const p = &radius; // 指向常量的常量指针
4. 数组和指针
在c++中,数组名实际上是指向数组第一个元素的常量指针,数组与指针关系密切。
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 using namespace std; int main(int argc, char *argv[]) { int list[6] = {1,2,3,4,5,6}; int* p = list; for(int i=0; i<6; i++) { cout << "Adress: " << (p+i) << " value: " << *(p+i) << " value: " << p[i] << endl; } return 0; }
5. 函数调用时传递指针参数
函数的参数可以是指针,指针参数可以通过值传递或者引用传递的方式进行传递
例如下面的swap()函数
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 using namespace std; void swap1(int a, int b) { int tmp = a; a = b; b = tmp; } void swap2(int& a, int& b) { int tmp = a; a = b; b = tmp; } void swap3(int* pa, int* pb) { int tmp = *pa; *pa = *pb; *pb = tmp; } void swap4(int* &a, int* &b) { int tmp = *a; *a = *b; *b = tmp; } int main(int argc, char *argv[]) { int a = 1; int b = 2; swap1(a,b); cout << "After swap a is " << a << " and b is " << b << endl; a = 1; b = 2; swap2(a, b); cout << "After swap a is " << a << " and b is " << b << endl; a = 1; b = 2; int* p1 = &a; int* p2 = &b; swap3(p1, p2); cout << "After swap a is " << a << " and b is " << b << endl; a = 1; b = 2; p1 = &a; p2 = &b; swap4(p1, p2); cout << "After swap a is " << a << " and b is " << b << endl; return 0; }
运行结果:
swap1()未实现交换
函数传引用, 形参相当于实参的一个别名
6. 从函数中返回指针
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 using namespace std; int* reverse(int*, int); void printArray(int*, int); int main(int argc, char *argv[]) { int size = 5; // 数组名不能动态定义 int a[5] = {1,2,3,4,5}; int* p = reverse(a, size); printArray(p, size); return 0; } int* reverse(int* list, int SIZE) { for(int i=0, j=SIZE-1; i<j; i++, j--) { int tmp = list[i]; list[i] = list[j]; list[j] = tmp; } return list; } void printArray(int* list, int SIZE) { for(int i=0; i<SIZE; i++) { cout << list[i]; } cout << endl; }
函数reverse()返回一个指针p。传入的第一个参数是实参(数组)的地址,实际上返回的也是同一个地址。
7,有用的数组函数
min_element
max_element
sort
random_shuffle
find:find函查找相应的元素,如果找到,返回的是数组元素对应的指针,如果没有找到,返回数组最后一个元素的后一个位置的指针,这就是为什么这些函数的参数为(list, list +size), 多出的一个指针应该就是分配给未找到元素时的这种情况。
这些对数组进行操作的函数都在头文件algotithm中,这些函数的参数都是指针,返回的都是相应元素的指针。
例子:
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 #include <algorithm> using namespace std; void printArray(int*, int); int main(int argc, char *argv[]) { int size = 6; int list[] = {4, 2, 3, 6, 5, 1}; printArray(list, size); int* min = min_element(list, list+6); // 参数是指针 int* max = max_element(list, list+6); // 参数 cout << "The minum is of list is " << *min << " at index " << (min - list) << endl; //注意这里的指针相减 cout << "The maxum is of list is " << *max << " at index " << (max - list) << endl; //注意这里的指针相减 random_shuffle(list, list+6); printArray(list, size); sort(list, list+6); printArray(list, 6); // find int key = 4; // 要查找的元素 int* p = find(list, list+6, key); if(p!=(list+6)) // 没找到要查找的元素 { cout << "The value " << *p << "is found at index " << (p-list) << endl; } else { cout << "The value " << *p << "is not found" << endl; } return 0; } void printArray(int* list, int SIZE) { for(int i=0; i<SIZE; i++) { cout << setw(3) << list[i]; } cout << endl; }
p != list+6, 证明找到了相应的元素
8,动态持久内存分配:
new操作符可以在运行时为基本数据类型,数组和对象分配持久的内存空间。
int *p = new int(4); //为一个整形变量动态分配内存空间, 将地址赋值给指针p cin >> size; int *list = new int[size]; //动态数组, 数组大小在程序运行时输入
动态数组, 数组大小在程序运行时输入,而创建一个普通数组时,数组的大小在程序编译时就已经定下来了
使用new操作符分配的内存是持久存在的,直到它被显式的释放或者程序退出。
delete p; //数
delete []p; // 数组
例子:重新实现数组reverse()
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 #include <algorithm> using namespace std; void printArray(int*, int); int* reverse(int*, int); int main(int argc, char *argv[]) { int SIZE = 10; int* p = new int[SIZE]; for(int i=0; i<SIZE; i++) { p[i] = i* 3 + 3; } printArray(p, SIZE); int* p1 = reverse(p, SIZE); printArray(p, SIZE); printArray(p1, SIZE); return 0; } int* reverse(int* list, int size) { int* result = new int[size]; for(int i=0,j=size-1; i<size; i++,j--) { result[i] = list[j]; } return result; } void printArray(int* list, int SIZE) { for(int i=0; i<SIZE; i++) { cout << setw(3) << list[i]; } cout << endl; }
C++中,局部变量的内存空间是在栈中分配的,而由new操作符分配的内存空间则出自称为“自由存储区域”或者“堆”的存储区域。
分配的空间一直是可用的,直至释放。
内存泄漏:
如下的代码:
int* p = new int; *p = 45; p = new int;
第一句: 将一个动态内存赋值给指针p;
第二局:给变量赋值45;
第三局: 重新给p赋值一个新的地址值
这样会导致保存p指向的内存空间改变,存储45的内存将无法再访问,而这段内存也无法释放,这就是内存泄漏。
9. 创建及访问动态对象:
调用对象的构造函数可以动态的创建对象,new className(arguments),对象的地址会被赋值给相应的指针,调用对象时任然用解引运算符*
例如:
int main(int argc, char *argv[]) { string* p1 = new string(); // 无参构造函数 string* p2 = new string("hello world"); // 有参数的构造函数 // 动态对象的调用 (*p2).substr(0,3); (*p1).append(" C++"); cout << (*p1) << endl; cout << (*p2) << endl; delete p1; delete p2; return 0; }
end
this指针:指向被调用的对象本身。
this指针是为了解决这里碰到的问题:https://blog.csdn.net/zj1131190425/article/details/82888131
即类中成员方法的参数名如果与数据域的变量名相同,就会出现覆盖的问题
例如之前的circle类: 成员函数的参数名不能命名为radius,因为radius是数据域
利用this指针就可以实现
circle类实现如下:
Circle::Circle(double radius) { this->radius = radius; number_of_obj++; // ++ } void Circle::setRadius(double radius) { this->radius = radius; }
9. 析构函数
每一个类都有一个析构函数,当对象销毁时将自动调用该函数,析构函数与构造函数是相对的概念,如果类中没有定义析构函数,编译器将自动定义一个缺省的析构函数,析构函数与构造函数一样,与类名相同,但前面加上~符号
作用: 回忆之前的Circle类, 其中数据域有一个static类型的变量。用来记录创建的对象的个数,每当创建一个对象,numberOfObject加一。 如果采用new运算符创建动态对象, 在对象调用完毕时,会使用delete删除指向对象的指针,这时对象会被销毁,就需要执行一个析构函数,实现numberOfObject减一。
Circle::~Circle() // 析构函数,在对象销毁时调用 { number_of_obj--; }
Course类的实现:
实现一个课程类,用于选课学生数据的保存:
数据域包括:
课程名 courseName
选课容量 capacity
选课人数 student_number
学生名单 students
类成员方法:
add_student() // 添加学生
drop_student() // 删除学生
get_student_list()
get_courseName()
get_student_number()
同时会用到构造函数和析构函数:
code:
course.h
// course类的定义 #ifndef COURSE_H #define COURSE_H #include <string> using namespace std; class Course { private: // 数据域 int courseCapacity; // 课程容量 int student_number; // 记录选课学生人数 string courseName; // 课程名称 string* students; // 存储学生的数组, string【capacity】 public: // 成员方法 Course(const string& courseName, int courseCapacity); // constructor ~Course(); // 析构函数 string getCourseName() const; // 只读函数 void addStudent(const string& name); void dropStudent(const string& name); string* getStudents() const; // 返回学生的名单 int getNumberOfStudent() const; }; #endif
course.cpp course类的实现
// 类的实现 #include "E:\back_up\code\c_plus_code\chapter8\external_file\course.h" #include <string> #include <iostream> using namespace std; Course::Course(const string& courseName, int courseCapacity) { this->courseName = courseName; this->courseCapacity = courseCapacity; students = new string[courseCapacity]; student_number = 0; } Course::~Course() { delete []students; // 删除students指针 } string Course::getCourseName() const { return courseName; } void Course::addStudent(const string& name) { if(student_number >= courseCapacity) { cout << "you cat not add student because the course is full." << endl; } else { students[student_number++] = name; } } void Course::dropStudent(const string& name) { bool found_flag = false; int index = 0; for(int i=0; i<student_number-1; i++) { if(name==students[i]) { found_flag = true; index = i; break; } } if(found_flag) { for(int i=index; i<student_number-2; i++) { students[i] = students[i+1]; } student_number--; cout << "Student " << name << " is successfully deleted from " << courseName << endl; } else // 如果学生不在名单中 { cout << "Student " << name << " is not in the course " << courseName << endl; } } string* Course::getStudents() const { return students; } int Course::getNumberOfStudent() const { return student_number; }
main.cpp
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 #include <algorithm> #include "E:\back_up\code\c_plus_code\chapter8\external_file\course.h" using namespace std; void printStudentList(string*, int); int main(int argc, char *argv[]) { Course course1("Mathg", 3); //Course course2("Data Analyse", 4); cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; course1.addStudent("Tonny"); course1.addStudent("Andrea"); cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; cout << "The student list: " << endl; printStudentList(course1.getStudents(), course1.getNumberOfStudent()); course1.addStudent("wang hau"); cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; cout << "The student list: " << endl; printStudentList(course1.getStudents(), course1.getNumberOfStudent()); course1.addStudent("huhuhu"); // 课程已满 course1.dropStudent("zhangjun"); // 删除不存在的学生 course1.dropStudent("Andrea"); cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; cout << "The student list: " << endl; printStudentList(course1.getStudents(), course1.getNumberOfStudent()); //Course course2("DataScience", 50); return 0; } void printStudentList(string* student_list, int num) { for(int i=0; i<num; i++) { cout << student_list[i] << endl; } cout << endl; }
运行结果:
拷贝构造函数:每一个类都有一个都有一个拷贝构造函数,用于拷贝对象。拷贝构造函数可以用来创建一个对象,并用另一个对象的数据初始化新建对象。每个类都会有一个缺省的拷贝构造函数
Circle(const Circle&)
拷贝构造函数和按成员赋值运算符(=)是对对象赋值采用的一种浅拷贝。
自定义拷贝构造函数,实现深拷贝:
Course类中:
代码:
course.h
// course类的定义 #ifndef COURSE_H #define COURSE_H #include <string> using namespace std; class Course { private: // 数据域 int courseCapacity; // 课程容量 int student_number; // 记录选课学生人数 string courseName; // 课程名称 string* students; // 存储学生的数组, string【capacity】 public: // 成员方法 Course(const string& courseName, int courseCapacity); // constructor ~Course(); // 析构函数 Course(const Course& course); // 拷贝构造函数 string getCourseName() const; // 只读函数 void addStudent(const string& name); void dropStudent(const string& name); string* getStudents() const; // 返回学生的名单 int getNumberOfStudent() const; }; #endif
course.cpp:
// 类的实现 #include "E:\back_up\code\c_plus_code\chapter8\external_file\course.h" #include <string> #include <iostream> using namespace std; Course::Course(const string& courseName, int courseCapacity) { this->courseName = courseName; this->courseCapacity = courseCapacity; students = new string[courseCapacity]; student_number = 0; } Course::~Course() { delete []students; // 删除students指针 } Course::Course(const Course& course) // 拷贝构造函数,自定义实现,深拷贝 { courseName = course.courseName; courseCapacity = course.courseCapacity; // deep copy student_number = course.student_number; students = new string[courseCapacity]; // deepcopy实现两个copy的对象数据域指针students应该相互独立, //如果用浅拷贝(默认的拷贝构造函数或者“=”浅拷贝。 //两个指针是指向相同的地址,这样在调用析构函数是, //同一个指针会delete两次,导致程序报错 for(int i=0; i<student_number; i++) { students[i] = course.students[i]; } } string Course::getCourseName() const { return courseName; } void Course::addStudent(const string& name) { if(student_number >= courseCapacity) { cout << "you cat not add student because the course is full." << endl; } else { students[student_number++] = name; } } void Course::dropStudent(const string& name) { bool found_flag = false; int index = 0; for(int i=0; i<student_number-1; i++) { if(name==students[i]) { found_flag = true; index = i; break; } } if(found_flag) { for(int i=index; i<student_number-2; i++) { students[i] = students[i+1]; } student_number--; cout << "Student " << name << " is successfully deleted from " << courseName << endl; } else // 如果学生不在名单中 { cout << "Student " << name << " is not in the course " << courseName << endl; } } string* Course::getStudents() const { return students; } int Course::getNumberOfStudent() const { return student_number; }
main.cpp
#include <iostream> #include <string> #include <iomanip> #include <sstream> // 将数字转化为字符串 #include <algorithm> #include "E:\back_up\code\c_plus_code\chapter8\external_file\course.h" using namespace std; void printStudentList(string*, int); int main(int argc, char *argv[]) { Course course1("Mathg", 3); //Course course2("Data Analyse", 4); cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; course1.addStudent("Tonny"); course1.addStudent("Andrea"); cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; cout << "The student list: " << endl; printStudentList(course1.getStudents(), course1.getNumberOfStudent()); course1.addStudent("wang hau"); cout << "course1 infomation: " << endl; cout << "course1's name is " << course1.getCourseName() << " and now it has " << course1.getNumberOfStudent() << " students" << endl; cout << "The student list: " << endl; printStudentList(course1.getStudents(), course1.getNumberOfStudent()); cout << "course2 infomation: " << endl; Course course2(course1); // course2 copy course1 cout << "course2's name is " << course2.getCourseName() << " and now it has " << course2.getNumberOfStudent() << " students" << endl; cout << "The student list: " << endl; printStudentList(course2.getStudents(), course2.getNumberOfStudent()); return 0; } void printStudentList(string* student_list, int num) { for(int i=0; i<num; i++) { cout << student_list[i] << endl; } cout << endl; }
运行结果:实现了对象的copy
为什么Course类中,要自定义拷贝构造函数,实现深拷贝?
原因::因为course类数据域有指向数组的指针students,如果使用上述的浅拷贝,在copy过程中,两个指针拷贝时,保存了相同地址,即指向了相同的地址。但是在程序执行完毕时,对象需要调用析构函数delete指针students,但是如果两个对象是copy的关系,则会调用两次析构函数删除相同的指针,这是程序就会报错。所以需要自定义拷贝构造函数,实现深拷贝。使两个对象中的数据域中指针students相互独立,就不会出现上述情况。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)