指针的用法和注意事项(三)

时间:2023-04-02 07:37:35

指针和*空间

指针实际上是一个变量,存储的是值的地址,而不是值本身。

#include <iostream>
using namespace std;

int main(){
    int donuts = 6;
    double cups = 4.5;

    cout << "donuts value = " << donuts;
    cout << " and donuts address = " << &donuts << endl;

    cout << "cups value = " << cups;
    cout << " and cups address = " << &cups << endl;

    return 0;
}

输出:

donuts value = 6 and donuts address = 0x7ffd156c32bc
cups value = 4.5 and cups address = 0x7ffd156c32c0

指针与C++基本原理

面向对象编程和传统的过程编程区别在于,OOP强调在运行阶段(而不是编译阶段)进行决策。运行阶段指的是程序正在运行时,编译阶段指的是编译器将程序组合起来时。

在面向对象编程中,数据和行为被组织成对象,对象之间通过消息传递进行交互。这意味着在运行时,程序可以根据对象的状态和接收到的消息来决定如何执行代码。因此,OOP确实强调在运行时进行决策,而不是在编译时。

相比之下,在传统的过程编程中,程序是被组织成一系列的过程或函数,这些过程或函数通过参数传递数据,完成一系列的计算操作。在这种情况下,程序在编译时被静态地分析和优化,以确定执行路径和优化代码

因此,虽然OOP确实强调在运行时进行决策,但这并不是唯一的区别,OOP和PP之间还存在其他许多差异。

指针存储的是变量的地址

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

int main(){
    int updates = 6;
    int *p_updates;
    p_updates = &updates;

    cout << "updates value = " << updates;
    cout << " p_updates value = " << *p_updates << endl;

    cout << "Address: &updates = " << &updates;
    cout << " , p_updates value = " << p_updates << endl;

    (*p_updates)++;
    cout << "updates value = " << updates << endl;

    return 0;
}

输出

updates value = 6 p_updates value = 6
Address: &updates = 0x7fffed2d322c , p_updates value = 0x7fffed2d322c
updates value = 7

声明和初始化指针

语法

typename *pointer_name;
typename *pointer_name = &variable;

示例

//pre是一个指向int型的指针,p是一个int型的变量
int *pre, p;	

// 声明一个指向int类型的指针,并初始化为nullptr
int* ptr = nullptr;

// 声明一个指向double类型的指针,并初始化为NULL
double* ptr2 = NULL;

// 声明一个指向char类型的指针,并初始化为0
char* ptr3 = 0;

// 声明一个指向int类型的指针,并初始化为一个变量的地址
int x = 10;
int* ptr4 = &x;

在这些示例中,我们声明了一个指向不同类型的指针,并使用特定的值将它们初始化。注意,第一种方式使用了C++11的新特性,使用nullptr替代了C语言中的NULL来初始化指针。

#include <iostream>
using namespace std;

int main(){
    int *pt = new int(1001);
    double* pd = new double(100.1);

    cout << "size of *pt = " << sizeof(*pt) << endl; //Output: 4
    cout << "size of *pd = " << sizeof(*pd) << endl; //Output: 8

    cout << "size of pt = " << sizeof(pt) << endl;   //Output: 8
    cout << "size of pd = " << sizeof(pd) << endl;	 //Output: 8

    return 0;
}

**注意:**虽然上面两个指针的类型不一致,但是可以看到他们的大小sizeof都是8,变量存储地址本身的长度一致,只是变量指向的数据类型一个是in一个是double

指针的危险

C++ 中指针的使用可能存在以下危险:

  1. 空指针:指针可能指向空地址,即不指向任何有效的内存单元。对空指针进行*解引用操作会导致程序崩溃。
  2. 野指针:指针可能指向已经释放的内存区域,这种指针称为野指针。对野指针进行解引用操作可能会导致不可预测的结果,比如程序崩溃、数据损坏等。
  3. 内存泄漏:如果指针指向动态分配的内存,并且在释放内存之前没有将指针设为 null,那么这个指针就会变成野指针,从而导致内存泄漏。
  4. 悬空指针:指针可能指向已经释放的内存区域,但是该内存区域的内容并没有立即被覆盖。这种指针称为悬空指针。对悬空指针进行解引用操作可能会导致不可预测的结果。
  5. 指针操作错误:对指针进行不当的操作,比如对一个指针加上一个整数,可能会导致指针指向错误的内存地址,从而导致程序崩溃或者数据损坏。

为了避免这些问题,程序员应该尽可能地使用智能指针和引用,避免手动分配和释放内存,以及在操作指针之前检查指针是否为空。同时,应该尽可能避免进行指针运算和类型转换等操作,以保证程序的正确性和稳定性。

指针和数字

指针不是整型,虽然计算机通常把地址当做整数处理。

在一些情况下,数字可以被解释为指针。例如,可以将一个整数转换为一个指向该整数所表示的地址的指针。这种操作通常不推荐,因为它可能导致访问无效的内存地址,从而导致程序崩溃或未定义的行为。

int *pt;
pt = 0xB8000000;	//type mismatch
pt = (int *)0xB8000000;

使用new来分配内存

指针可以在运行阶段分配未命名的内存以存储值。

typename *pointer_name = new typename;

C++中使用newdelete来动态分配和释放内存。下面是一个简单的示例:

#include <iostream>

int main() {
    // 分配一个整型变量的内存空间
    int *p = new int;

    // 检查是否成功分配内存
    if (p == nullptr) {
        std::cout << "Failed to allocate memory." << std::endl;
        return 1;
    }

    // 初始化变量并输出结果
    *p = 42;
    std::cout << "The value of p is " << *p << std::endl;

    // 释放内存
    delete p;

    return 0;
}

在上面的代码中,我们首先使用new分配了一个整型变量的内存空间,并将其地址存储在指针变量p中。然后,我们检查了是否成功分配了内存空间,并对该变量进行了初始化和输出。最后,我们使用delete释放了分配的内存空间。

需要注意的是

  • 使用new分配内存时,需要在程序结束前使用delete手动释放分配的内存空间,否则会导致内存泄漏。
#include <iostream>
#include <cstring>
using namespace std;

int main(){
    int nighs = 1001;
    int *pt = new int;
    *pt = 1001;

    cout << "nights value = " << nighs << " location: "  << &nighs << endl;
    cout << "*pt = " << *pt << " location: "  << pt << endl;
    
    double* pd = new double(100001.0);
    cout << "*pds = " << *pd << " location: "  << pd << endl;

    cout << "size of pt = " << sizeof(pt) << " size of *pt = " << sizeof(*pt) << endl;
    cout << "size of pd = " << sizeof(pd) << " size of *pd = " << sizeof(*pd) << endl;
    
    delete pt, pd;

    return 0;
}

输出:

nights value = 1001 location: 0x7ffd073bd0c4
*pt = 1001 location: 0x562205be3eb0
*pds = 100001 location: 0x562205be42e0
size of pt = 8 size of *pt = 4
size of pd = 8 size of *pd = 8

使用delete释放内存

使用new向堆空间中申请内存,相应的在使用完之后需要将内存还给内存池,需要用到delete。

int *p = new int(10)
delete p;

释放p指向的内存,但不会删除指针p本身。

注意:

  1. 只能释放通过newnew[]分配的内存,否则会导致未定义的行为。使用delete释放非动态分配的内存、栈内存或全局内存都是错误的。
  2. 对同一块内存空间只能使用一次delete,多次释放同一块内存空间会导致未定义的行为。
  3. 当使用delete释放数组内存空间时,需要使用delete[],而不是delete,否则会导致未定义的行为。反之,当使用delete释放非数组内存空间时,不能使用delete[],否则也会导致未定义的行为。
  4. 在程序结束之前,需要释放通过newnew[]分配的所有内存空间,否则会导致内存泄漏。
  5. 当多个指针指向同一块内存空间时,需要确保只有一个指针调用delete释放内存,否则会导致未定义的行为。同时,释放内存之后,需要将指针设置为nullptr,以避免野指针问题。

总之,使用delete释放内存需要非常谨慎,避免出现未定义的行为和内存泄漏问题。

使用new来创建动态数组

语法

typename *pointer_name = new typename[num_elements]; 

pointer_name指向数组的第一个元素的地址

大部分情况下,可以将*等价于[],这是因为在c和c++内部使用的是指针来处理数组。

int* pt = new int [5];
pt[0] = 1, pt[1] = 2, pt[2] = 3, pt[3] = 4, pt[4] = 5;

for (int i = 0; i < 5; i++)
	cout << *(pt + i) << " ";

cout << endl;			//Output : 1 2 3 4 5 

cout << *pt << endl;	//Output : 1
pt = pt + 1;
cout << *pt << endl;	//Output : 2

指针、数组和算术运算

对于上面的代码,pt = pt + 1实际上将指针指向了第二个元素所在的位置

于指针而言,指针变量+1后,其增加的值等于指向的类型占用的字节数。

#include <iostream>
using namespace std;

int main(){
    double wages[] = {1000.0, 2000.0, 3000.0};
    short stacks[] = {3, 2, 1};

    double *pw = wages; //equal to *pw = wages[0], pw = wages o  pw = &wages[0]
    short *ps = stacks; //equal to *ps = stacks[0]

    cout << "pw = " << pw << " *pw = " << *pw << endl;
    pw = pw+1;
    cout << "add 1 to the pw pointer : \n";
    cout << "pw = " << pw << " *pw = " << *pw << endl << endl; 

    cout << "ps = " << ps << " *ps = " << *ps << endl;
    ps = ps+1;
    cout << "add 1 to the pw pointer : \n";
    cout << "ps = " << ps << " *ps = " << *ps << endl << endl; 

    cout << "access two elements with array notation\n"; //利用数组的方式来进行访问
    cout << "stack[0] = " << stacks[0] << ", stack[1] = " << stacks[1] << endl; 
    cout << "access two elements with pointer notation\n";//通过指针的方式来进行访问
    cout << "*stack = " << stacks[0] << ", *(stack + 1) = " << *(stacks + 1) << endl << endl;

    cout << sizeof(wages) << " = size of wages array" << endl;
    cout << sizeof(stacks) << " = size of stacks array" << endl;
    cout << sizeof(stacks[0]) << " = size of stacks array" << endl; 

    return 0;
}

输出

pw = 0x7ffd40a94130 *pw = 1000
add 1 to the pw pointer : 
pw = 0x7ffd40a94138 *pw = 2000	//指针实际增加的是指向类型占用的字节数,指针增加8 double型

ps = 0x7ffd40a9412a *ps = 3
add 1 to the pw pointer : 
ps = 0x7ffd40a9412c *ps = 2		//指针增加2,short 型

access two elements with array notation	  	//使用方括号的方式来访问数组
stack[0] = 3, stack[1] = 2
access two elements with pointer notation	//使用指针的方式来进行访问
*stack = 3, *(stack + 1) = 2

24 = size of wages array		//sizeof(arrayName)得到的是数组的大小
6 = size of stacks array	
2 = size of stacks array		//sizeof(arrayName[0])数组元素占用的字节数

常用int n = sizeof(arrayName) / sizeof(arrayName[0])得到数组的长度。

虽然指针和数组都表示地址,但时他们之间还是有区别的。

  • 指针是变量,可以修改指针的值,但数组名是常量

  • 对指针使用sizeof得到的是地址的长度,而sizeof(arrayName)得到的是数组的长度。

    cout << sizeof(pw) << " = size of pw double pointer" << endl;
    cout << sizeof(ps) << " = size of ps short pointer" << endl;
    //8 = size of pw double pointer
    //8 = size of ps short pointer
    

数组的地址

int num[10];
cout << num << endl;
cout << &num << endl;

cout << (num + 1) << endl;
cout << (&num + 1) << endl;

输出

0x7ffe677095a0		//数组名
0x7ffe677095a0		//&数组名
0x7ffe677095a4		//num + 1
0x7ffe677095c8		//(&num + 1) - &num得到40

注意:数组名被解释为第一个元素的地址,而对数组取地址会得到整个数组的地址。

虽然这两个地址相同,但是&num[0]是一个4字节内存块的地址,(&num + 1)是一个40字节内存块的地址。

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

int (*pt)[10] = &num;   //pt指向包含10个元素的int数组,pt还是指针只不过指向的类型是int []
for (int i = 0; i < 10; i++)
	cout << (*pt)[i] << " "; //equal to :cout << *((*pt) + i) << " ";
cout << endl;

区别

int *pt[10];			//pt优先与[]结合,pt是一个int*数组
pt[0] = num;
for (int i = 0; i < 10; i++)
cout << *(pt[0] + i) << " "; 

小结

//1、声明指针
typename* pointer_name;

//2、给指针复制
pointer_name = new typename;    //equal to : typename* pointer_name = new typename;
pointer_name = &variable;       //equal to : typename* pointer_name = &variable;

//3、对指针解除引用,来获取指针所值地址上的值
cout << *pointer_name << endl;

//4、区分指针和指针所指向的值
pointer_name;   //存储的是变量的地址 address
*pointer_name;  //解引用*,指向对应地址上的值 value

//5、数组名
typename arr_name[element_size];
pointer = arr_name; //equal to :  pointer = &arr_name[0];

//6、指针算术,两个指针指向同一个数组时,可以获得两个之间所指元素的间隔
int nums[] = {1, 1, 2, 3, 5, 8, 13};
int n = sizeof(nums) / sizeof(nums[0]);
int *p1 = &nums[0], *p2 = &nums[n-1];
int diff = p2 - p1;
cout << "diff between p1 and p2 = " << diff << endl;

//7、数组的静态联编和动态联编
int nums[10];   //数组的长度在编译时设置,静态联编
int size;
cin >> size;
int* pt = new int [size]; 
//在运行时维数组分配空间,使用new申请的空间,在结束之后应该使用delete释放

//8、数组表示法和指针表示法
*(pt + i) == pt[i];     //两种表示法等价

指针和字符串

char数组名、char指针以及括号引起的字符串常量都被解释为字符串第一个字符的地址。在cout输出时,会从该字符开始打印,直到遇到空字符位置。

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

int main(){
    char animal[] = "bear";
    const char * bird = "wren"; //const在*左边,是一个指针常量,即指针所指的是一个常量
    char *ps;   

    cout << animal << " and " << bird << endl;
    //cout << ps;   可能会出错,ps指针并未被初始化,没有志向任何字符串

    cout << "Enter a kind of animal : ";
    cin.getline(animal, 20);
    //cin >> ps;    ps并未指向任何空间,不能对ps直接进行使用

    ps = animal;
    cout << ps << "!\n";
    cout << "Before using strcpy : \n";
    cout << animal << " and its address : " << (int *)animal << endl;
    //直接输出animal显示的是字符串,利用(int*)强制类型转换
    cout << ps << " and its address : " << (int *)ps << endl;

    ps = new char[strlen(animal) + 1];
    strcpy(ps, animal);
    cout << "After using strcpy : \n";
    cout << animal << " and its address : " << (int *)animal << endl;
    cout << ps << " and its address : " << (int *)ps << endl;

    bird = "bird";
    cout << bird << endl;//虽然不可以修改bird位置中的内容,但是可以对指针常量重新赋值

    delete [] ps;

    return 0;
}

输出

bear and wren
Enter a kind of animal : China Panada
China Panada!
Before using strcpy : 
China Panada and its address : 0x7ffc90ab5443	
China Panada and its address : 0x7ffc90ab5443	//ps = animal,ps指向animal的位置
After using strcpy : 
China Panada and its address : 0x7ffc90ab5443
China Panada and its address : 0x55608e87b6d0	//strcpy(ps, animal),ps被重新赋值
bird									     //可以对指针常量重新赋值

使用new创建动态结构

创建结构体用于定义双向链表的结点。

#include <iostream>
using namespace std;

struct DListNode{
    int key, val;
    DListNode* pre, *next;
    DListNode(int k, int v):key(k), val(v), pre(nullptr), next(nullptr){}
};

int main(){
    DListNode* dummy = new DListNode(-1, -1);

    cout << "key : " << dummy->key << " val : " << dummy->val << endl; 
	//创建动态结构时不能将成员运算符.用于结构名,因为这种结构没有名称只知道它的地址
    //如果结构标识符是结构名,则使用句号运算符
    //如果结构标识符是指向结构的指针,那么应该使用->运算符
    
    return 0;
}