21天学通C++(四)基础知识4

本文介绍了C++中指针的概念,包括声明、使用和内存地址操作。讲解了new和delete用于动态内存分配,以及引用的作用和使用场景。重点强调了内存管理和避免内存泄露的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天是基础知识的最后一节~明天开始是C++面向对象编程基础,介绍类的概念,以及C++如何支持封装、抽象、继承和多态等重要的面向对象编程原则。

通过这一小节,应该要学到:

  • 什么是指针;
  • 什么是自由存储区;
  • 如何使用运算符new和delete分配和释放内存;
  • 如果使用指针和动态分配编写稳定的应用程序;
  • 什么是引用;
  • 指针和引用的区别;
  • 什么情况下使用指针,什么情况下使用引用。

阐述指针和引用

1.1 什么是指针

        指针是存储内存地址的变量。就像int变量用于存储数值一样,指针变量用于存储内存地址。

        因此,指针是一个变量,与所有变量一样,指针也占用内存空间,指针的特殊之处在于,指针包含的值被解读为内存地址,因此指针是一种指向内存单元的特殊变量。

内存单元地址通常使用十六进制表示法。显示十六进制数时,通常使用前缀0x。

1.1.1 声明指针

        作为一种变量,指针也需要声明。通常将指针声明为指向特定的类型,如int,这意味着指针包含的地址对应的内存单元存储了一个整数。也可以将指针声明为指向一个内存块,这种指针被称为void指针。

PointedType * PointerVariableName;

        与大多数变量一样,除非对指针进行初始化,否则它包含的值将是随机的。不希望访问随机的内存地址,因此将指针初始化为NULL。NULL是一个可检查的值,且不会是内存地址:

PointedType * PointerVariableName = NULL;

        因此,声明int指针的代码如下:

int *pointsToInt = NULL;

与所有数据类型一样,除非对指针进行初始化,否则它包含的将是垃圾值。对指针来说,这种垃圾值非常危险,因为指针包含的值被视为地址。未初始化的指针可能导致程序访问非法内存单元,进而导致程序崩溃。

1.1.2 使用引用运算符(&)获取变量的地址

        如果varName是一个变量,&varName将是存储该变量的内存的地址。

引用运算符(&)也叫地址运算符。

1.1.3 使用指针存储地址

        通过上面已经知道如何声明指针以及如何获取变量的地址,还知道指针是用于存储内存地址的变量。现在该将它们关联起来,使用指针存储使用引用运算符(&)获取的地址。

        假设声明了某种类型的变量:

// Declaring a variable 
Type VariableName = InitialValue;

        要将该变量的地址存储到一个指针中,需要声明一个同样类型的指针,并使用引用运算符(&)将其初始化为该变量的地址:

// Declaring a pointer to Type and initializing to address 
Type* Pointer = &Variable;
1.1.4 使用解除引用运算符(*)访问指向的数据

        有了包含合法地址的指针后,要访问这个地方,即获取或设置这个地方的数据,可以使用解除引用运算符(*)。

解除引用运算符(*)也叫间接运算符。

1.1.5 将sizeof()用于指针的结果

        指针是包含内存地址的变量。因此无论指针指向哪种类型的变量,其内容都是一个地址——一个数字。在特定的系统中,存储地址多需的字节数是固定的。因此,将sizeof()用于指针时,结果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关。

1.2 动态内存分配
1.2.1 使用new和delete动态地分配和释放内存

        使用new来分配新的内存块。通常情况下,如果成功,new将返回指向一个指针,指向分配的内存,否则将引发异常。使用new时,需要指定要为哪种数据类型分配内存:

Type* Pointer = new Type; // request memory for one element

        需要为多个元素分配内存时,还可指定要为多少个元素分配内存:

Type* Pointer = new Type[numElements]; // request memory for numElements

 因此,如果需要给整型分配内存,可使用如下语法:

int* pointToAnInt = new int; // get a pointer to an integer 
int* pointToNums = new int[10]; // pointer to a block of 10 integers

new表示请求分配内存,并不能保证分配请求总能得到满足,因为这取决于系统的状态以及内存资源的可用性。

        使用new分配的内存最终都需使用对应的delete进行释放:

Type* Pointer = new Type; // allocate memory 
delete Pointer; // release memory allocated above

        这种规则也适用于为多个元素分配的内存:

Type* Pointer = new Type[numElements]; // allocate a block 
delete[] Pointer; // release block allocated above

对于使用new[...]分配的内存块,需要使用delete[]来释放;对于使用new为单个元素分配的内存,需要使用delete来释放。

        不再使用分配的内存后,如果不释放它们,这些内存仍被预留并分配给应用程序。这将减少可供其他应用程序使用的系统内存量,甚至降低应用程序的执行速度。这被称为内存泄露,应不惜一切代价避免这种情况发生。

运算符new和delete分配和释放自由存储区中的内存。自由存储区是一种内存抽象,表现为一个内存池,应用程序可分配(预留)和释放其中的内存。

1.2.2 将递增和递减运算符用于指针的结果

        将指针递增或递减时,其包含的地址将增加或减少指向的数据类型的sizeof(并不一定是1字节)。这样,编译器将确保指针不会指向数据的中间或末尾,而只会指向数据的开头。

        如果声明了如下指针:

Type* pType = Address;

        则执行++pType后,pType将包含(指向)Address + sizeof(Type)。

1.2.3 将关键字const用于指针

        通过将变量声明为const的,可确保变量的取值在整个生命周期内都固定为初始值。这种变量的值不能修改,因此不能将其用作左值。

        指针也是变量,因此也可将关键字const用于指针。然而,指针是特殊的变量,包含内存地址,还可用于修改内存中的数据块。因此,const指针有如下三种。

  • 指针包含的地址是常量,不能修改,但可修改指针指向的数据。
int daysInMonth = 30; 
int* const pDaysInMonth = &daysInMonth; 
*pDaysInMonth = 31; // OK! Data pointed to can be changed 
int daysInLunarMonth = 28; 
pDaysInMonth = &daysInLunarMonth; // Not OK! Cannot change address!
  • 指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方。
int hoursInDay = 24; 
const int* pointsToInt = &hoursInDay; 
int monthsInYear = 12; 
pointsToInt = &monthsInYear; // OK! 
*pointsToInt = 13; // Not OK! Cannot change data being pointed to 
int* newPointer = pointsToInt; // Not OK! Cannot assign const to non-const
  • 指针包含的地址以及它指向的值都是常量,不能修改。
int hoursInDay = 24; 
const int* const pHoursInDay = &hoursInDay; 
*pHoursInDay = 25; // Not OK! Cannot change data being pointed to 
int daysInMonth = 30; 
pHoursInDay = &daysInMonth; // Not OK! Cannot change address

        将指针传递给函数时,这些形式的const很有用。函数参数应声明为最严格的const指针,以确保函数不会修改指针指向的值,这可精致修改指针及其指向的数据。

1.2.4 将指针传递给函数

        指针是一种将内存空间传递给函数的有效方式,其中可包含函数完成其工作所需的数据,也可包含操作结果。将指针作为函数参数时,确保函数只能修改希望它修改的参数很重要。为控制函数可修改哪些参数以及不能修改哪些参数,可使用关键字const。

1.2.5 数组和指针的类似之处

        当声明下面的int数组时:

int myNumbers[5];

        编译器将分配固定数量的内存,用于存储5个整数;同时提供一个指向数组中第一个元素的指针,而指针由指定的数组名标识。换句话说,myNumbers是一个指针,指向第一个元素(myNumbers[0])。

        可将数组变量赋给类型与之相同的指针,也证明了数组与指针类似,存储在指针中的地址与数组第一个元素在内存中的地址相同。

        由于数组变量就是指针,因此也可将用于指针的解除引用运算符(*)用于数组。同样,可将数组运算符([])用于指针。

1.3 使用指针时常犯的编程错误
1.3.1 内存泄露

        这可能是C+应用程序最常见的问题之一:运行时间越长,占用的内存越多,系统越慢。。如果在使用new动态分配的内存不再需要后,程序员没有使用配套的delete释放,通常就会出现这种情况。

1.3.2 指针指向无效的内存单元

        使用运算符*对指针解除引用,以访问指向的值时,务必确保指针指向了有效的内存单元,否则程序要么崩溃,要么行为不端。

1.3.3 悬浮指针

        使用delete释放后,任何有效指针都将无效。为了避免这种问题,在初始化指针或释放指针后将其设置为NULL,并在使用运算符*对指针解除引用前检查它是否有效(将其与NULL比较)。

1.4 指针编程最佳实践

        务必初始化指针变量,否则它将包含垃圾值,这些垃圾值被解读为地址,但应用程序并未获得访问这些地方的授权。如果不能将指针初始化为new返回的有效地址,可将其初始化为NULL。

        务必仅在指针有效时才使用它,否则程序可能崩溃。

        对于使用new分配的内存,一定要记得使用delete进行释放,否则应用程序将泄露内存,进而降低系统的性能。

1.5 引用是什么

        引用是变量的别名。声明引用时,需要将其初始化为一个变量,因此引用只是另一种访问相应变量存储的数据的方式。

        要声明引用,可使用引用运算符(&)。

VarType original = Value; 
VarType& ReferenceVariable = original;
1.5.1 是什么让引用很有用

        引用能够访问相应变量所在的内存单元,这使得编写函数时引用很有用。

        典型的函数声明类似于下面这样:

ReturnType DoSomething(Type parameter);

        调用函数DoSomething()的代码类似于下面这样:

ReturnType Result = DoSomething(argument); // function call

        上述代码导致将argument的值复制为Parameter,再被函数DoSomething()使用。如果argument占用了大量内存,这个复制步骤的开销很大。用于,用DoSomething()返回值时,这个值被复制给Result。如果能避免这些复制步骤,让函数直接使用调用者栈中的数据就太好了。为此,可使用引用。

        可避免复制步骤的函数版本类似于下面这样:

ReturnType DoSomething(Type& parameter); // note the reference&

        调用该函数的代码类似于下面这样:

ReturnType Result = DoSomething(argument);

        由于argument是按引用传递的,Parameter不再是argument的拷贝,而是它的别名。

1.5.2 将关键字const用于引用

        可能需要禁止通过引用修改它指向的变量的值,为此可在声明引用时使用关键字const。

int original = 30; 
const int& constRef = original; 
constRef = 40; // Not allowed: constRef can’t change value in original 
int& ref2 = constRef; // Not allowed: ref2 is not const
const int& constRef2 = constRef; // OK
1.5.3 按引用向函数传递参数

        引用的优点之一是,可避免将实参复制给形参,从而极大地提高性能。然而,让被调用的函数直接调用函数栈时,确保被调用函数不能修改调用函数中的变量很重要。为此,可将引用声明为const的。

1.6 总结

        本小节介绍了指针和引用。

        学习了指针,它可用来访问和操纵内存,还是帮助动态分配内存的工具。

        还介绍了new和delete,它们可以用于为单个元素分配和释放内存;还介绍了new[...]和delete[],它们可用于为数组分配和释放内存。

        还简要的了解了指针编程和动态内存分配的陷阱,知道释放动态分配的内存至关重要,有助于避免内存泄露。

        引用是别名,将参数传递给函数时,引用可很好地替代指针,因为引用总是有效的。

        学习了const指针和const引用,知道声明函数时应尽可能提高参数的const程度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值