目录
指针的基本概念
1.变量的地址
在c++中,每定义一个变量,系统就会给变量分配一块内存,内存是有地址的.
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a;
char b;
bool c;
string d;
//以字符串形式打印,直到读到/0停
cout << "变量a的地址是: " << &a << endl;
cout << "变量b的地址是: " << &b << endl;
cout << "变量c的地址是: " << &c << endl;
cout << "变量d的地址是: " << &d << endl;
//十六进制正确表示地址
cout << "变量a的地址是: " << (void*)& a << endl;
cout << "变量b的地址是: " << (void*)& b << endl;
cout << "变量c的地址是: " << (void*)& c << endl;
cout << "变量d的地址是: " << (void*)& d << endl;
//十进制正确表示地址
cout << "变量a的地址是: " << (long long)& a << endl;
cout << "变量b的地址是: " << (long long)& b << endl;
cout << "变量c的地址是: " << (long long)& c << endl;
cout << "变量d的地址是: " << (long long)& d << endl;
}
C++用运算符&获取变量再内存中的起始地址,语法如下:
&x : 取出变量x的地址
2.指针变量
指针变量简称指针,他是一种特殊的变量,专门用于存放在内存中的起始地址
语法 : 数据类型 * 变量名
*号表示这个变量是指针
3.对指针赋值:
语法 : 指针 = &变量名
如:
int *pa = &a;
注意:
-
对指针的赋值操作也通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”。
-
如果指针的数据类型与基类型不符,编译会出现警告。但是,可以强制转换它们的类型。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a;
char b;
bool c;
string d;
int* pa = &a;
char* pb = &b;
bool* pc = &c;
string* pd = &d;
cout << "变量a的地址是: " << (long long)& a << endl;
cout << "变量b的地址是: " << (long long)& b << endl;
cout << "变量c的地址是: " << (long long)& c << endl;
cout << "变量d的地址是: " << (long long)& d << endl;
puts("");
cout << "变量a的地址是: " << (long long)pa << endl;
cout << "变量b的地址是: " << (long long)pb << endl;
cout << "变量c的地址是: " << (long long)pc << endl;
cout << "变量d的地址是: " << (long long)pd << endl;
// 两个输出相同
return 0;
}
4.指针占用的内存
指针也是一个变量(指针变量),所以也要占用内存空间.
在64位的操作系统中,不管是什么类型的指针,占用的内存都是8字节
在c++中,指针是一种复合数据类型,复合数据类型是指基于其他类型而定义的数据类型。
如:在程序中,int是整型类型,int*是整型指针类型
int*可以用于声明变量,也可以用于sizeof运算符,也可以用于数据类型的强制转换。
总的来说:
把int*当作一种数据类型就ok了。
使用指针
声明变量后,在没有赋值之前,是不能使用指针的,vs会报错!
(星号)运算符被称为 间接值 或 解除引用(解引用) 运算符,将他用于指针,可以得到该地址的内存中存储的值,(星号)也是乘法符号,c++会自动判断类型.
变量和指向变量的指针就像同一枚硬币的两面
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a = 3;
int* p = &a;
cout << "a=" << a << endl; // 3
cout << "*p=" << *p << endl; // 3
*p = 8;
cout << "a=" << a << endl; // 8
cout << "*p=" << *p << endl; // 8
return 0;
}
但是多个指针可以指向同一个变量
#include <iostream>
using namespace std;
int main()
{
int a = 3;
int* p1 = &a;
int* p2 = &a;
cout << "a=" << a << endl; // 3
cout << "*p1=" << *p1 << endl; // 3
cout << "*p2=" << *p2 << endl; // 3
*p1 = 8;
cout << "a=" << a << endl; // 8
cout << "*p1=" << *p1 << endl; // 8
cout << "*p2 =" << *p2 << endl;// 8
cout << "&a=" << &a << ",p1=" << p1 << ",p2=" << p2 << endl;//三个相同
return 0;
}
指针的理解
用const修饰的指针
-
常量指针
-
指针常量
-
常指针常量
1.常量指针
语法 : const 数据类型* 变量名
注意:
-
不能通过解引用的方法来修改内存地址中的值(用原始的变量名是可以修改的)
-
指向的变量(对象)可以改变
-
一般用于修饰函数的形参,不希望在函数里修改内存地址中的值
-
如果形参的值不需要改变,建议加上const修饰,程序的可读性会更好
#include <iostream>
using namespace std;
int main()
{
//常量指针的用法 :const 数据类型 * 变量名
//不能通过解引用的方法来修改内存地址中的值(用原始的变量名是可以修改的)
int a = 3;
int b = 8;
const int* p = &a;
a = 13;
// *p = 13; //会报错
cout << "a=" << a << ",*p=" << *p << endl;
return 0;
}
2.指针常量
语法 : 数据类型 * const 变量名
c++编辑器中把指针常量做了一些特别的处理,改头换面之后,叫做引用;
#include <iostream>
using namespace std;
int main()
{
//指针常量的用法 : 数据类型 * const 变量名
//指向的变量(对象)不可以改变
int a = 3, b = 8;
int* const p = &a;//指针常量 : 在定义的时候必须初始化,否则没有意义
*p = 13;//可以通过解引用的方法修改内存地址中的值
//p = &b;//报错
cout << "a=" << a << ",*p=" << *p << endl;
return 0;
}
3.常指针常量
语法 :
#include <iostream>
using namespace std;
int main()
{
int a = 8;
const int* const p = &a;
return 0;
}
常指针常量 : 指向的对象不可改变,也不能通过解引用的方法去修改内存地址中的值
c++中叫做 : 常应用
记忆秘诀:*表示指针,指针在前先读指针;指针在前指针就不允许改变。
void关键字
在c++中,void表示无类型,有以下三个用途:
1.函数返回值用void,表示函数无返回值
void print(){ cout<<"hello"<<endl; }
2.函数形参填void,表示函数不需要参数:
int f(void){ return 0; }
3.函数的形参用void*,表示接受任意数据类型的指针
#include <iostream>
using namespace std;
// 只关心地址本身,不关心里面的内容,用void*可以存放任意类型的地址
//显示变量的十六进制地址的函数:varname-变量名,p-变量的地址。
void func(string varname, void* p) {
cout << varname << "的地址是: " << p << endl;
}
int main()
{
// 显示变量的十六进制地址
int a = 8;
char b;
cout << "a的地址是: " << &a << endl;
cout << "b的地址是: " << &b << endl;//会出现"烫烫烫..."
func("a", &a);
func("b", &b);
return 0;
}
-
不能用void声明变量,他不能代表一个真实的变量
-
不能对void*指针直接解引用(需要转换成其他类型的指针)。
-
把其他类型的指针赋值给void*指针不需要转换
-
把void*类型的指针赋值给其他类型指针需要转换
C++内存模型:
内存模型图 :
-
栈:存储局部变量、函数参数和返回值。
-
堆:存储动态开辟内存的变量。
-
数据段:存储全局变量和静态变量。
-
代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改。
动态分配内存 new 和 delete
使用堆区内存有四个步骤:
-
1.声明一个指针;
-
2.用new运算符向系统申请一块内存,让指针指向这块内存;
-
3.通过对指针解引用的方法,像使用变量一样使用这块内存;
-
4.如果这块内存不用了,用delete运算符释放它;
#include <iostream>
using namespace std;
int main()
{
//申请内存: new 数据类型(初始值); c++11支持{}
//释放内存的语法:delete
int* p = new int(5);
cout << "*p = " << *p << endl; //5
*p = 8;
cout << "*p = " << *p << endl; // 8
delete p;
return 0;
}
注意:
-
动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。
-
如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。
-
动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收
-
就算指针的作用域已失效,所指向的内存也不会释放。
-
用指针跟踪已分配的内存时,不能跟丢。
二级指针
二级指针 : 指针用于存放普通变量的地址,二级指针用于存放指针变量的地址
语法 : 数据类型** 指针
#include <iostream>
using namespace std;
int main()
{
int ii = 8;
cout << "ii=" << ii << ",ii的地址是: " << &ii << endl;
int* pii = ⅈ
cout << "pii=" << pii << ",pii的地址是:" << &pii << ",*pii=" << *pii << endl;
int** ppii = &pii;
cout << "ppii=" << ppii << ",ppii的地址是: " << &ppii << ", *ppii= " << *ppii << endl;
return 0;
}
运行截图:
使用指针有两个目的:
-
1.传递地址
-
2.存放动态分配的内存的地址
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参用二级指针。
把普通变量的地址传入函数后可以在函数中修改变量的值;把指针的地址传入函数后可以在函数中指针的值。
#include <iostream>
using namespace std;
void func(int** pp) {
*pp = new int(3);
cout << "pp=" << pp << ",*pp=" << *pp << endl;
// pp=000000ECBAAFFB38,*pp=00000221E18004D0
}
int main()
{
int* p = 0;
func(&p);
cout << "p=" << p << ",*p=" << *p << endl;
// p=00000221E18004D0,*p=3
}
空指针
在c/c++中,都用0或NULL来表示空指针
声明指针后,在赋值之前,让他指向空,表示没有指向任何地址
注意:
-
如果对空指针进行解引用,程序会崩溃(读取访问权限冲突)
-
如果对空指针使用delete,系统将忽略该操作,不会出现异常,所以,内存被释放后,也应该将指针指向空。
在函数中,应该有判断形参是否为空指针的代码(保证程序的健壮性)
为什么空指针访问会出现异常?
NULL指针分配的分区:其范围是从 0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。
c++ 11 的nullptr
用0和NULL来表示空指针会有歧义,c++11建议使用nullptr来表示空指针,即:(void*)0;
-std=c++11
野指针
野指针就是指针指向的不是一个有效(合法)的地址
在程序中,如果访问野指针,可能会造成程序的崩溃
#include<iostream>
using namespace std;
//int* func() {
// int a = 3;
// cout << "a=" << a << "&a=" << &a << endl;
// return &a;
//}
int main() {
// 野指针
// int* p = (int* )0x7647832823; //随便写的,没有实际含义
// cout << "p=" << p << ",*p=" << *p << endl;
//出现野指针的三种情况
// 1.在定义指针的时候,未对指针进行初始化,他的值是不确定的
//int* a;
// 2.指针指向了动态分配的内存,内存被释放后,指针不会置空,但指向的地址已经失效
//int* b = new int(3);
//delete b;
//cout << "b=" << b << ",*b=" << *b << endl;
// 3.指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收)
// 让指针指向了函数的局部变量,
//或者把函数的局部变量的地址作为返回值赋给了指针。
// int* p = func();
// cout << "p=" << p << ",*p=" << *p << endl;//p=0000004445AFF694,*p=-858993460
}
规避方法:
1)指针在定义的时候,如果没地方指,就初始化为nullptr。
2)动态分配的内存被释放后,将其置为nullptr。
3)函数不要返回局部变量的地址。
注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度。
函数指针:
函数的二进制代码存放在代码段,函数的地址是它在内存中的起始地址,如果把函数的地址作为参数传递给函数,就可以在函数中灵活地调用其他的函数
使用函数指针地三个步骤:
-
声明函数指针
-
让函数指针指向函数地地址
-
通过函数指针调用函数
函数指针地使用:
#include<iostream>
using namespace std;
void func(int no, string str) {
cout << "亲爱的" << no << "号: " << str << endl;
}
int main()
{
int bh = 3;
string message = "我是一只傻傻鸟!";
func(bh, message);//普通函数调用方法
//函数指针
void(*pfunc)(int, string); // 声明表白函数地函数指针
pfunc = func; // 对函数指针赋值,语法是函数指针名 = 函数名
pfunc(bh, message); // 用函数指针名调用函数。c++
(*pfunc)(bh, message); // c语言写法
return 0;
}
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void zs(int a) // 张三的个性化表白函数。
{
cout << "a=" << a << "我要先翻三个跟斗再表白。\n"; // 个性化表白的代码。
}
void ls(int a) // 李四的个性化表白函数。
{
cout << "a=" << a << "我有一只小小鸟。\n"; // 个性化表白的代码。
}
void show(void (*pf)(int), int b)
{
cout << "表白之前的准备工作已完成。\n"; // 表白之前的准备工作。
pf(b); // 用函数指针名调用个性化表白函数。
cout << "表白之后的收尾工作已完成。\n"; // 表白之后的收尾工作。
}
int main()
{
show(zs, 3); // 张三要表白。
show(ls, 4); // 李四要表白。
}