指针
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 删除。