前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

指针

原创
作者头像
用户10731060
发布2023-08-30 20:55:30
1540
发布2023-08-30 20:55:30
举报
文章被收录于专栏:初学C++初学C++

指针

1.1 指针的基本概念

指针的作用:可以通过指针间接访问内存

内存编号时从0开始记录的,一般用十六进制数字表示

可以利用指针变量保存地址.

|注意:声明指针变量后,在没有赋值前,里面都是乱七八糟的值,这时候不能使用指针

1.2 指针变量的定义和使用

指针变量定义语法: 数据类型 * 变量名;

#define _CRT_SECURE_NO_WARNINGS 1

#include "test.h"

int main()

{

//1、定义指针

int a = 10;

//指针定义的语法 : 数据类型 * 指针变量名;

int * p;

//让指针记录变量a的地址

p = &a;

cout << "a的地址为: " << &a << endl;

// 000000A73899F734

cout << "指针p为: " << p << endl;

// 000000A73899F734

//2、使用指针

//可以通过 解引用 的方式来找到 指针 指向的内存

// 指针前加 * 代表解引用,找到指针指向的内存中的数据

cout << "a = " << *p << endl;

//a=10

//对指针 指向的内存 重新赋值

*p = 1000;

cout << "a = " << a << endl; //1000

cout << "*p = " << *p << endl; //1000

}

1.3 指针所占的内存空间

提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?

无论什么数据类型,32位操作系统下都是4个字节,64位操作系统下都是8个字节

#define _CRT_SECURE_NO_WARNINGS 1

#include "test.h"

int main()

{

//指针所占内存空间

int a = 10;

int* p = &a;

cout << "sizeof(int *) = " << sizeof(p) << endl;

// 32位操作系统下都是4个字节,64位操作系统下都是8个字节

cout << "sizeof(char *) = " << sizeof(p) << endl;

// 32位操作系统下都是4个字节,64位操作系统下都是8个字节

cout << "sizeof(short *) = " << sizeof(p) << endl; // 32位操作系统下都是4个字节,64位操作系统下都是8个字节

cout << "sizeof(long *) = " << sizeof(p) << endl;

// 32位操作系统下都是4个字节,64位操作系统下都是8个字节

cout << "sizeof(long long *) = " << sizeof(p) << endl;

// 32位操作系统下都是4个字节,64位操作系统下都是8个字节

return 0;

}

1.4 空指针 和 野指针

空指针:指针变量指向内存中 编号 为 0 的 空间

用途:初始化指针变量

注意:空指针指向的内存是不可以访问的

0 - 255 之间的内存地址 是 系统占用的,我们只要访问它 就会出错

int main()

{

int* p = NULL;

*p = 100;

//引发了异常: 写入访问权限冲突。p 是 nullptr/。 nullptr是空指针异常

return 0;

}

野指针:指针变量 指向 非法的内存空间

int main()

{

//野指针

int * p = (int *)0x1100;

cout << "*p" << *p << endl;

//引发了异常: 读取访问权限冲突。p 是 0x1100。

return 0;

}

野指针就相当于是 你去开了一间房,拿到了房卡,但是你却用这张卡去开另一间的房门。

在程序中尽量避免野指针和空指针

|总结:空指针 和 野指针 都不是 我们申请的空间,因此不要访问。

1.5 const 修饰 指针

const 修饰 指针 有三种情况:

1、const修饰指针 --- 常量指针 指针指向的值不能修改,但是指向可以修改,指向可以修改表示,内存地址改变,即指针位置发生了变化,但是原位置的值没变

2、const修饰常量 --- 指针常量 指针指向的值可以修改,指针指向不能修改,即指针位置不变,但是值发生了变化

3、const即修饰指针,又修饰常量

|注意:指针常量和常量指针体现在数组中为 一个是指针的位置没有发生变化,但是值发生了变化,一个是指针的位置发生了变化,值没变

int main()

{

int a = 10;

int b = 10;

//常量指针,修饰的是指针

//指针指向的值不可以修改,指针的指向可以修改

const int* p1 = &a;

//*p1 = 20; // 错误

p1 = &b;

//指针常量,修饰的是常量

//指针指向的值可以修改,指针的指向不可以修改

int * const p2 = &a;

*p2 = 20;

//p2 = &b; //错误

cout << p1 << endl;

//即修饰指针,又修饰常量

//指针的指向不可以修改,指针指向的值也不可以修改

const int* const p3 = &a;

//*p3 = 20; //错误

//p3 = &b; //错误

}

|技巧:看const 右侧紧跟着的是指针还是常量,是指针就是常量指针,是常量就是指针常量

1.6 指针和数组

作用:利用指针访问数组中元素

int main()

{

int arr[] = { 1,2,13,4,5,6,7,8,9 };

cout << "第一个元素:" << arr[0] << endl;

//arr就是数组的首地址

int* p = arr;

//通过解引用的方式获取数组的第一个元素

cout << "数组的第一个元素:" << *p << endl;

//因为指针p也是int类型,所以 进行 ++ 操作后,指针 就会 向右 偏移 4个字节

p++;

cout << "数组的第二个元素:" << *p << endl;

//通过循环的方式遍历指针,获取 数组中 的每一个元素

for (int i = 1; i < 10; i++)

{

cout << "数组的第" << i + 1 << "个元素为:" << *p << endl;

p++;

}

return 0;

}

1.7 指针和函数

作用:利用指针作函数参数,可以修改实参的值

1.8 指针、数组、函数(cpp22.cpp)

案例描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排序

例如数组:int arr[10] = {1,6,58,12,35,12,15,16,12,15}

1、定义(test.cpp)和声明(test.h)一个冒泡排序的函数

void sort(int* arr,int length);

//通过地址传递的方式 封装 一个 冒泡排序 的函数,通过 常量指针 的方式交换 内存地址

void sort(int* arr,int length)

{

for (int i = 0; i < length; i ++)

{

//指针常量,指针指向的值可以修改,指针的指向不能修改

int* const internalTemp = &arr[i];

for (int j = i + 1; j < length; j++)

{

//指针常量,指针指向的值可以修改,指针的指向不能修改

int* const temp = &arr[j];

if (*internalTemp > *temp)

{

int newVal = *internalTemp;

*internalTemp = *temp;

*temp = newVal;

}

}

}

}

2、主函数,调用 封装 的 通过指针进行冒泡排序 的函数

#define _CRT_SECURE_NO_WARNINGS 1

#include "test.h"

int main()

{

int arr[] = { 15,21,12,16,15,19,1,51,14,19,18,12 };

int* p = arr;

//获取数组占用的总内存

int totalMemory = sizeof(arr);

//获取数组中一个元素的内存

int oneMemory = sizeof(arr[0]);

//获取数组的长度

int length = totalMemory / oneMemory;

sort(p,length);

for (int i = 0; i < length; i++)

{

cout << *p << endl;

p++;

}

return 0;

}

1.9 数组指针和指针数组

1.10 一维数组和指针

1)指针的算数

将一个整型变量加1后,其值将增加1.

但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。

int main()

{

int a = 0;

char b;

cout << "a的内存地址为:" << &a << endl;

//00000063B214F594

cout << "b的内存地址为: " << (void *) & b << endl;

//00000063B214F5B4

cout << "a 取址后加 1:" << (void*)(&a + 1) << endl;

//00000063B214F598

cout << "b 取址后加 1:" << (void*)(&b + 1) << endl;

//00000063B214F5B5

return 0;

}

2)数组的地址

a) 数组在内存中占用的空间是连续的。

b) C++将数组名解释为数组第0个元素的地址。

c) 数组第0个元素的地址和数组首地址的取值是相同的。

d)数组第n个元素的地址是:数组首地址 + n。

e)C++编译器 把 数组名[下标] 解释为 *(数组首地址 + 下标)。

3)数组的本质

数组是占用连续空间的一块内存,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。

4)数组名不一定会被解释为地址

在多数情况下,C++ 将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。

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

cout << (& a[2])[0] << endl; //第三个元素的地址[0] 解释为 *(第三个元素的地址+0) // 得到的是第三个元素的值

3)一维数组用于函数的参数

一维数组用于函数的参数时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。

书写方法有两种:

void func(int * arr,int len);

void func(int arr[],int len); //注意这种写法传递的也是数组的指针

注意:

在函数中,可以用数组表示法,也可以用指针表示法。

在函数中,不要对指针名用sizeof运算符,它不是数组名。

4)用new动态创建一维数组

普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。

动态创建一维数组的语法:数据类型 * 指针 = new 数据类型[数组长度];

释放一维数组的语法: delete[] 指针;

注意:

动态创建的数组没有数组名,不能用sizeof运算符。

可以用数组表示法和指针表示法两种方式使用动态创建的数组。

必须使用delete[]来释放内存(不能只用delete,否则只会释放第一个元素的内存)。

不要用delete[] 释放同一个内存块两次(否则等同于操作野指针)。

对空指针用delete[] 是安全的(释放内存后,应该把指针置空nullptr)。

声明普通数组时,数组长度可以用变量,相当于在栈上动态创建数组,并且不需要释放。

如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。

int main()

{

long *a = new (std::nothrow)long[1000000000];

if (a == nullptr)

{

cout << "数组长度过长" << endl;

}

else

{

a[999999999] = 10;

delete[] a;

}

return 0;

}

为什么delete[]释放数组时不需要指定数组的大小?因为数组会自动跟踪已分配数组的内存。

1.11 二维数组用于函数的参数

1) 行指针(数组指针)

声明行指针的语法:数据类型 (*行指针名)[行的大小]; //行的大小即数组长度。

例:int (*p1)[3]; //p1是行指针,用于指向数组长度为3的int型数组。

int (*p2)[5]; //p2是行指针,用于指向数组长度为5的int型数组。

double (*p3)[5]; //p3是行指针,用于指向数组长度为5的double型数组。

一维数组名被解释为数组第0个元素的地址。

对一维数组名 取地址 得到的是 数组的地址,是 行地址。

int main()

{

int arr[10];

cout << "数组arr第0个元素的地址:" << (long long)arr << endl;

cout << "数组arr的地址:" << (long long)&arr << endl;

cout << "数组arr第0个元素的地址+1:" << (long long)(arr + 1) << endl;

//相对于第0个元素增加了4个字节,即获得的是第二个元素的地址

cout << "数组arr的地址+1:" << (long long)( & arr + 1 )<< endl;

//相对于整个数组增加了40个字节,即大了一个数组的长度

return 0;

}

2) 二维数组名是行地址

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

bh是二维数组名,该数组有两个元素,每一个元素本身又是一个数组长度为3的整型数组。

bh被解释为数组长度为3的整型数组类型的行地址。

如果存放bh的值,要用数组长度为3的整型数组类型的行指针(即存放的是这个二维数组的首个元素,即第一个一维数组)。

int (*p)[3] = bh; /存放的是这个二维数组的首个元素,即第一个一维数组

int bh[2][3] = {{1,3,2},{6,4,5}}; int (*p)[3] = bh; //正确 int * p = bh; //错误

3) 把二维数组传递给函数

如果要把bh传递给函数,函数的声明如下:

void func(int (*p)[3],int len);

void func(int p[][3],int len);

//这是第一种写法,再写个第二种写法 void func(int(*p)[3], int len)

{

for (int i = 0; i < len; i++)

{

for (int j = 0; j < 3; j++)

{

cout << (*p + i)[j] << " ";

}

}

}

//这是第二种写法 void func(int p[][3],int len)

{

for(int i =0; i < len; i ++)

{

for(int j = 0; j < 3; j++)

{

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

}

}

}

int main()

{

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

func(arr, 2);

return 0;

}

1.11 多维数组(cpp37.cpp)

定义一个三维数组

int main()

{

//假设有4个超女方阵,每个超女方阵有4行,每行有3个超女

int bn[4][4][3];

int i = 1;

for (int a = 0; a < 4; a++)

{

for (int b = 0; b < 4; b++)

{

for (int c = 0; c < 3; c++)

{

bn[a][b][c] = i++;

}

}

}

for (int a = 0; a < 4; a++)

{

for (int b = 0; b < 4; b++)

{

for (int c = 0; c < 3; c++)

{

cout << bn[a][b][c] << "\t";

}

cout << endl;

}

cout << endl;

}

return 0;

}

int bh[4][4][3];

bh是三维数组名,该数组有4个元素,每个元素本身又是一个2行3列的二维数组。

bh被解释为2行3列的二维数组类型的二维地址。

如果存放bh的值,要用 2行3列 的二维数组 类型的行指针。

int (*p)[2][3] = &bh;

定义一个函数为三维数组bn赋值

//定义一个函数为三维数组bh赋值

void func(int(*p)[4][3])

{

int i = 1;

for (int a = 0;

a < 4; a++)

{

for (int b = 0; b < 4; b++)

{

for (int c = 0; c < 3; c++)

{

p[a][b][c] = i++;

}

}

}

}

int main()

{

//假设有4个超女方阵,每个超女方阵有4行,每行有3个超女

int bn[4][4][3];

func(bn);

for (int a = 0; a < 4; a++)

{

for (int b = 0; b < 4; b++)

{

for (int c = 0; c < 3; c++)

{

cout << bn[a][b][c] << "\t";

}

cout << endl;

}

cout << endl;

}

return 0;

}

1.12 函数指针和回调函数

函数的二进制代码存放再内存四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以再函数中灵活的调用其它函数。

使用函数指针的三个步骤:(cpp38.cpp)

a) 声明函数指针;

b) 让函数指针指向函数的地址;

c) 通过函数指针调用函数;

1)声明函数指针

声明普通指针时,必须提供指针的类型。同样,声明函数指针时,也必须提供函数类型,函数的类型是指返回值和参数列表(函数名和形参名不是)

假设函数的原型是:

int func1(int no,string str);

int func2(int id,string message);

int func3(int no,string message);

bool func4(int id,string info);

bool func5(int id);

则函数指针的声明是:

func1\2\3: int (*p)(int,string);

func4: bool (*p)(int,string);

func5: bool (*p)(int);

void func(int* p, string* p1) { cout << *p << " " << *p1 << endl; } int main() { int p = 10; string str = "bb"; //使用函数指针的三个步骤: //1.定义函数指针 void (*pfunc)(int *p, string * p1); //2.让函数指针指向函数的地址 pfunc = &func; //3.通过函数指针调用函数 pfunc(&p, &str); return 0; }

为什么会有函数指针?

函数指针主要是用于回调函数,即我们定义一个函数,这个函数去调用另一个函数,在调用另一个函数之前我们有通用的逻辑,调用之后有通用的逻辑,但是这个被调用的这个函数可以是个性化的(返回值和参数类型要相同),针对这个个性化的函数,可以是不同函数名的函数,同时内部逻辑完全是不一样的,只要参数的类型和函数返回值相同即可。

回调函数是把一个函数的代码嵌入到另一个函数中,调用者函数提供了主体的流程和框架,具体的流程可以由回调函数实现。

大致意思如下:

void 个性化表白函数() { } void 表白神器(个性化表白函数指针p) { //表白之前的准备工作 个性化表白函数p(); //表白之后的工作 } int main() { 表白神器(个性化表白函数指针p); return 0; }

函数指针的使用:(cpp39.cpp)

void biaobai1() { cout << "一年之后,刘钟二人在上海相遇,互诉心声" << endl; } void biaobai2() { cout << "一年之后,刘钟二人在成都相遇,互诉心声" << endl; } void show(void (*pfunc)()) { cout << "准备开始表白" << endl; pfunc(); cout << "表白结束" << endl; } int main() { //调用回调1 //show(biaobai1); //调用回调2 show(biaobai2); return 0; }

回调函数中加参数的方式:

void biaobai1(string str) { cout << str << endl; cout << "一年之后,刘钟二人在上海相遇,互诉心声" << endl; } void biaobai2() { cout << "一年之后,刘钟二人在成都相遇,互诉心声" << endl; } void show(void (*pfunc)(string),string str) { cout << "准备开始表白" << endl; pfunc(str); cout << "表白结束" << endl; } int main() { //调用回调1 //show(biaobai1); //调用回调2 show(biaobai1,"钟来上海"); return 0; }

1.13 指针函数(一般没什么用)

返回值为地址的函数就是指针函数

1.14 二级指针

指针是指针变量的简称,也是变量,是变量就有地址。

指针用于存放普通变量的地址。

二级指针用于存放指针变量的地址。

声明二级指针的语法:数据类型** 指针名;

使用指针有两个目的:1、传递地址;2、存放动态分配的内存的地址;

在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址的地址,形参用二级指针。

把普通变量的地址传入函数后可以在函数中修改变量的值,把指针的地址传入函数后可以在函数中修改指针的值。

#pragma once

#include <iostream>

void func(int ** pp)

{

*pp = new int(3);

} int main()

{

int* p;

func(&p);

std::cout << "p = " << *p << std::endl;

std::cout << "int * p = " << p << std::endl;

std::cout << "int ** p = " << &p << std::endl;

return 0;

}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档