阐述C++中的指针和引用

该博客围绕C++指针展开,介绍了指针的概念、声明、使用,包括获取地址、存储地址、访问指向数据等操作。还阐述了动态分配内存,使用new和delete运算符,以及指针编程中常见错误,如内存泄漏、悬浮指针等,最后给出指针编程的最佳实践。

本篇博客的内容

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

什么是指针?

所谓的指针也是一种变量,不过它存储的是内存的地址。它是一种指向内存单元的特殊变量。内存单元地址通常使用的是十六进制表示法(前缀为0x)。

声明指针

指针作为一种变量也需要声明和初始化。未初始化的指针可能导致程序访问非法内存单元,进而导致程序崩溃。

PointedType * PointerVariableName = NULL; //initializing value

int * pointsToInt = NULL;

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

#include <iostream>
using namespace std;

int main()
{
   int age = 30;
   const double Pi = 3.1416;

   // Use & to find the address in memory
   cout << "Integer age is located at: 0x" << &age << endl;
   cout << "Double Pi is located at: 0x" << &Pi << endl;

   return 0;
}

本程序使用引用运算符(&)获取变量age和常量Pi的地址。作为一种约定,显示十六进制数时,应加上文本0x。

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

使用指针存储地址

#include <iostream>
using namespace std;

int main()
{
   int age = 30;
   int* pointsToInt = &age;

   // Displaying the value of pointer
   cout << "Integer age is at: 0x" << hex << pointsToInt << endl;

   return 0;
}

本程序,使用指针来存储使用引用运算符获得的地址。

#include <iostream>
using namespace std;

int main()
{
   int age = 30;

   int* pointsToInt = &age;
   cout << "pointsToInt points to age now" << endl;

   // Displaying the value of pointer
   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;

   int DogsAge = 9;
   pointsToInt = &DogsAge;
   cout << "pointsToInt points to DogsAge now" << endl;

   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;

   return 0;
}

本程序,不同的内存地址赋给指针变量,让它指向不同的值。

使用解除引用运算符(*)访问指向的数据

#include <iostream>
using namespace std;

int main()
{
   int age = 30;
   int dogsAge = 9;

   cout << "Integer age = " << age << endl;
   cout << "Integer dogsAge = " << dogsAge << endl;

   int* pointsToInt = &age;
   cout << "pointsToInt points to age" << endl;

   // Displaying the value of pointer
   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;

   // Displaying the value at the pointed location
   cout << "*pointsToInt = " << dec << *pointsToInt << endl;

   pointsToInt = &dogsAge;
   cout << "pointsToInt points to dogsAge now" << endl;

   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;
   cout << "*pointsToInt = " << dec << *pointsToInt << endl;

   return 0;
}

使用解除运算符(*)访问指针指向的内存单元中的值。解除运算符(*)也叫间接运算符。

#include <iostream>
using namespace std;

int main()
{
   int dogsAge = 30;
   cout << "Initialized dogsAge = " << dogsAge << endl;

   int* pointsToAnAge = &dogsAge;
   cout << "pointsToAnAge points to dogsAge" << endl;

   cout << "Enter an age for your dog: ";

   // store input at the memory pointed to by pointsToAnAge
   cin >> *pointsToAnAge;  

   // Displaying the address where age is stored
   cout << "Input stored at 0x" << hex << pointsToAnAge << endl;

   cout << "Integer dogsAge = " << dec << dogsAge << endl;

   return 0;
}

本程序是将*pointsToInt作为左值,即给它赋值的情况。

将sizeof()用于指针的结果

#include <iostream>
using namespace std;

int main()
{
   cout << "sizeof fundamental types -" << endl;
   cout << "sizeof(char) = " << sizeof(char) << endl;
   cout << "sizeof(int) = " << sizeof(int) << endl;
   cout << "sizeof(double) = " << sizeof(double) << endl;

   cout << "sizeof pointers to fundamental types -" << endl;
   cout << "sizeof(char*) = " << sizeof(char*) << endl;
   cout << "sizeof(int*) = " << sizeof(int*) << endl;
   cout << "sizeof(double*) = " << sizeof(double*) << endl;

   return 0;
}

将运算符sizeof( )作用于char,int,double得到的结果分别是1,4,8。但是将sizeof( )作用于char*,int*,double*得到的结果都是4。我的笔电测试出来是这样的。

动态分配内存

int myNums[100]; //100个整型的静态数组

静态数组存在的一些问题:首先限制了程序的容量,无法存储100个以上的数字;如果只需存储1个数字,却为100 个数组预留存储空间,这降低了系统的性能。所以我们就需要动态分配内存了,C++提供了两个运算符new和delete。指针是包含内存地址的变量,在高效地动态分配内存方面起到十分重要的作用。

使用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;
int * pointToNums = new int[10];

使用new 分配的要用delete来释放:

Type * Pointer = new Type;
delete Pointer;

Type * Pointer = new Type[numElements];
delete[] Pointer;

ps:自己new的一定要自己delete,否则会造成内存泄漏,我们应该尽量避免这一情况的发生。

#include <iostream>
using namespace std;

int main()
{
   // Request for memory space for an int
   int* pointsToAnAge = new int;

   // Use the allocated memory to store a number
   cout << "Enter your dog's age: ";
   cin >> *pointsToAnAge;

   // use indirection operator* to access value 
   cout << "Age " << *pointsToAnAge << " is stored at 0x" << hex << pointsToAnAge << endl;

   delete pointsToAnAge; // release dynamically allocated memory

   return 0;
}

PS:delete 和 new是配套使用的,除此以外不能将delete用于任何包含地址的指针。

#include <iostream>
#include <string>
using namespace std;

int main()
{
   cout << "How many integers shall I reserve memory for?" << endl;
   int numEntries = 0;
   cin >> numEntries;

   int* myNumbers = new int[numEntries];

   cout << "Memory allocated at: 0x" << myNumbers << hex << endl;

   // de-allocate before exiting
   delete[] myNumbers;

   return 0;
}

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

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

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

Type * pType = Address;

// 执行++pType后,pType将包含(指向)Address + sizeof(Type)
#include <iostream>
using namespace std;

int main()
{
   cout << "How many integers you wish to enter? ";
   int numEntries = 0;
   cin >> numEntries;

   int* pointsToInts = new int [numEntries];

   cout << "Allocated for " << numEntries << " integers" << endl;
   for(int counter = 0; counter < numEntries; ++counter)
   {
      cout << "Enter number "<< counter << ": ";
      cin >> *(pointsToInts + counter); 
   }

   cout << "Displaying all numbers entered: " << endl;
   for(int counter = 0; counter < numEntries; ++counter)
      cout << *(pointsToInts++) << " ";

   cout << endl;

   // return pointer to initial position
   pointsToInts -= numEntries;

   // done with using memory? release 
   delete[] pointsToInts;

   return 0;
}

上面的示例程序写地不地道,在循环遍历的时候,一般会重新定义一个变量来指向首地址,移动新定义的指针遍历,而不是原来的指针pointsToInts。

将关键字const用于指针

const 指针有如下三种类型:

指针包含的地址是常量,不能修改,但可以修改指针指向的数据。

int daysInMonth = 30;
int* const pDaysInMonth = &daysInMonth; // 指针包含的地址是常量
*pDaysInMonth = 31 //OK!
int daysInLunarMonth = 28;
pDaysInMonth = &daysInLunarMonth // Not OK! 

指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方。

int hoursInDay = 24;
const int* pointsToInt = &hoursInDay;
int monthInYear = 12;
pointsToInt = &monthInYear; //OK
*pointsToInt = 13; //Not OK
int* newPointer = pointsToInt; //Not OK! Cannot assign const to non-const

指针包含的地址以及指向的值都是常量,不能修改。

int hoursInDay = 24;
const int* const pHoursInDay = &hoursInDay;
*pHoursInDay = 25; // Not OK!
int daysInMonth = 30;
pHoursInDay = &daysInMonth; // Not OK!

将指针传递给函数

 

#include <iostream>
using namespace std;

void CalcArea(const double* const ptrPi, // const pointer to const data
              const double* const ptrRadius, // i.e. no changes allowed
              double* const ptrArea)  // can change data pointed to
{
   // check pointers for validity before using!
   if (ptrPi && ptrRadius && ptrArea) 
      *ptrArea = (*ptrPi) * (*ptrRadius) * (*ptrRadius);
}

int main()
{
   const double Pi = 3.1416;

   cout << "Enter radius of circle: ";
   double radius = 0;
   cin >> radius;

   double area = 0;
   CalcArea (&Pi, &radius, &area);

   cout << "Area is = " << area << endl;

   return 0;
}

上面的程序使用了两种指针:ptrPi和ptrRadius被声明为“指向const数据的const指针”,所以两者都不能修改。ptrArea是const指针,不能修改指针的值(地址),但可以修改它指向的数据。

数组和指针的类似之处

数组变量名是指向第一个元素的指针,可将数组变量赋给类型与之相同的指针。

#include <iostream>
using namespace std;

int main()
{
   // Static array of 5 integers
   int myNumbers[5];

   // Pointer initialized to array
   int* pointToNums = myNumbers;

   // Display address contained in pointer
   cout << "pointToNums = 0x" << hex << pointToNums << endl;

   // Address of first element of array
   cout << "&myNumbers[0] = 0x" << hex << &myNumbers[0] << endl;

   return 0;
}

数组的两种访问方式。

#include <iostream>
using namespace std;

int main()
{
   const int ARRAY_LEN = 5;

   // Static array of 5 integers, initialized
   int myNumbers[ARRAY_LEN] = {24, -1, 365, -999, 2011};

   // Pointer initialized to first element in array
   int* pointToNums = myNumbers;

   cout << "Display array using pointer syntax, operator*" << endl;
   for (int index = 0; index < ARRAY_LEN; ++index)
      cout << "Element " << index << " = " << *(myNumbers + index) << endl;

   cout << "Display array using ptr with array syntax, operator[]" << endl;
   for (int index = 0; index < ARRAY_LEN; ++index)
      cout << "Element " << index << " = " << pointToNums[index] << endl;

   return 0;
}

PS:自己new的,一定要自己delete!!!

使用指针时常犯的编程错误

 不同于C#和Java等基于运行时环境的新语言,C++没有自动垃圾收集器对程序已分配但不能使用的内存进行清理。

内存泄漏

#include <iostream>
using namespace std;

int main()
{
   // uninitialized pointer (bad)
   bool* isSunny; 

   cout << "Is it sunny (y/n)? ";
   char userInput = 'y';
   cin >> userInput;

   if (userInput == 'y')
   {
      isSunny = new bool;
      *isSunny = true;
   }

   // isSunny contains invalid value if user entered 'n'
   cout << "Boolean flag sunny says: " << *isSunny << endl;

   // delete being invoked also when new wasn't 
   delete isSunny;

   return 0;
}

上面的代码,我用gcc跑完之后,发现输入n的时候,*isSunny输出的是131。我也是有点懵逼。

悬浮指针

#include <iostream>
using namespace std;

int main()
{
	cout << "Is it sunny (y/n)? ";
	char userInput = 'y';
	cin >> userInput;

	// declare pointer and initialize
	bool* const isSunny = new bool;
	*isSunny = true;

	if (userInput == 'n')
		*isSunny = false; 

   cout << "Boolean flag sunny says: " << *isSunny << endl;

   // release valid memory
   delete isSunny;

	return 0;
}

上面的程序是对前一版程序的改版,这样的代码安全性更高,使我们应该学习的代码。

检查使用new发出的分配请求是否得到满足

#include <iostream>
using namespace std;

// remove the try-catch block to see this application crash
int main()
{
   try
   {
      // Request a LOT of memory!
      int* pointsToManyNums = new int [0x1fffffff];

      // Use the allocated memory

	  delete[] pointsToManyNums;
   }
   catch (bad_alloc)
   {
      cout << "Memory allocation failed. Ending program" << endl;
   }
   return 0;
}
运行结果

上面的代码采用了异常抛出的处理方法(向用户报告异常之后)再退出程序。

#include <iostream>
using namespace std;

int main()
{
   // Request LOTS of memory space, use nothrow
   int* pointsToManyNums = new(nothrow) int [0x1fffffff];

   if (pointsToManyNums) // check pointsToManyNums != NULL
   {
      // Use the allocated memory
      delete[] pointsToManyNums;
      //cout<<"jump in."<<endl;
   }
   else
      cout << "Memory allocation failed. Ending program" << endl;

   return 0;
}

上面的程序使用了变种的new(nothrow),它在程序内存分配失败时不引发异常,而是返回NULL,让你在使用指针前检查其有效性。

指针编程最佳实践

 引用是什么

 引用就是变量的别名。

#include <iostream>
using namespace std;

int main()
{
   int original = 30;
   cout << "original = " << original << endl;
   cout << "original is at address: " << hex << &original << endl;

   int& ref1 = original;
   cout << "ref1 is at address: " << hex << &ref1 << endl;

   int& ref2 = ref1;
   cout << "ref2 is at address: " << hex << &ref2 << endl;
   cout << "Therefore, ref2 = " << dec << ref2 << endl;

   return 0;
}

上面的程序说明了如何声明并使用变量。

是什么让引用很有用?

在函数的编写中,其形参使用引用参数,可以避免传参时候的值拷贝,以及接收返回值时候的值拷贝。函数还可以用引用参数来返回函数的结果。

#include <iostream>
using namespace std;

void GetSquare(int& number)
{
   number *= number;
}

int main()
{
   cout << "Enter a number you wish to square: ";
   int number = 0;
   cin >> number;

   GetSquare(number);
   cout << "Square is: " << number << endl;

   return 0;
}

将关键字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 allowed
const int& constRef2 = constRef; //OK 

 按引用向函数传递参数

引用的优点之一就是,可以避免将形参复制给形参,从而极大地提高性能。

#include <iostream>
using namespace std;

void GetSquare(const int& number, int& result)
{
   result = number*number;
}

int main()
{
   cout << "Enter a number you wish to square: ";
   int number = 0;
   cin >> number;

   int square = 0;
   GetSquare(number, square);
   cout << number << "^2 = " << square << endl;

   return 0;
}

上面的程序在编写函数的时候,用来const。在编程中使用const可以提高编程的质量。在多人协作的时候,更加凸显这样的优点。


 特此声明:本篇博客是学习《21天学通C++》的记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值