c++指针的使用
指针基本概念
C++用运算符 & 获取变量在内存中的起始地址
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a;
char b;
bool c;
string d;
//cout输出地址中,char类型的地址输出有 BUG
cout<<"变量a的地址是"<<&a<<endl;
cout<<"变量b的地址是"<<&b<<endl;
cout<<"变量c的地址是"<<&c<<endl;
cout<<"变量d的地址是"<<&d<<endl;
cout<<"-------------------"<<endl;
//解决方法1(16进制显示地址)
cout<<"变量a的地址是"<<(void*)&a<<endl;
cout<<"变量b的地址是"<<(void*)&b<<endl;
cout<<"变量c的地址是"<<(void*)&c<<endl;
cout<<"变量d的地址是"<<(void*)&d<<endl;
cout<<"-------------------"<<endl;
//解决方法2(10进制显示地址)
cout<<"变量a的地址是"<<(long long)&a<<endl;
cout<<"变量b的地址是"<<(long long)&b<<endl;
cout<<"变量c的地址是"<<(long long)&c<<endl;
cout<<"变量d的地址是"<<(long long)&d<<endl;
cout<<"-------------------"<<endl;
}
指针变量
专用于存放变量在内存中的起始地址
指针变量 简称 指针
对指针(指针变量)赋值
- 对指针的赋值操作也通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”。
- 管是整型、浮点型、字符型,还是其它的数据类型的变量,它的地址都是一个十六进制数
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a=1;
char b='b';
bool c = true;
string d = "ddd";
int* a1;
char* b1;
bool* c1;
string* d1;
//指针赋值
a1 = &a;
b1 = &b;
c1 = &c;
d1 = &d;
//获取指针变量所指向的变量的值
cout<<"变量a的值为:"<<*a1<<endl;
cout<<"变量b的值为:"<<*b1<<endl;
cout<<"变量c的值为:"<<*c1<<endl;
cout<<"变量d的值为:"<<*d1<<endl;
}
指针占用的内存
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int* a1;
char* b1;
bool* c1;
string* d1;
cout<<sizeof(a1)<<endl;
cout<<sizeof(b1)<<endl;
cout<<sizeof(c1)<<endl;
cout<<sizeof(d1)<<endl;
}
使用指针
*运算符被称为间接值或解除引用(解引用)运算符,将它用于指针,可以得到该地址的内存中存储的值
指针用于函数参数
地址传递:把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址,在函数中通过解引用的方法直接操作内存中的数据,可以修改实数的值
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
// 调用函数的时候,调用者把数值赋给了函数的参数。
// 实参:调用者程序中书写的在函数名括号中的参数。
// 形参:函数的参数列表。
void func(int *no, string *str) // 向超女表白的函数。
{
cout << "亲爱的" << *no << "号:" << *str << endl;
*no = 8;
*str = "我有一只小小鸟。";
}
// 写一个函数,从3名超女的身高数据中,选出最高的和最矮的。
void func1(int a, int b, int c, int* max, int* min)
{
*max = a > b ? a : b; // 取a和b中的大者。
*min = a < b ? a : b; // 取a和b中的小者。
*max = *max > c ? *max : c; // 取*max和c中的大者。
*min = *min < c ? *min : c; // 取*min和c中的小者。
}
int main()
{
int bh = 3; // 超女的编号。
string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
func(&bh, &message); // 调用向超女表白的函数。
/*{
int *no = &bh;
string *str = &message;
cout << "亲爱的" << *no << "号:" << *str << endl;
*no = 8;
*str = "我有一只小小鸟。";
}*/
cout << "亲爱的" << bh << "号:" << message << endl;
// 从3名超女的身高数据中,选出最高的和最矮的。
int a = 180, b = 170, c = 175, m, n;
func1(a, b, c, &m, &n);
cout << "m=" << m << ",n=" << n << endl;
}
const修饰指针
常量指针
不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(const int *no, const string *str) // 向超女表白的函数。
{
// *no = 8;
// *str = "我有一只小小鸟。";
cout << "亲爱的" << *no << "号:" << *str << endl;
}
int main()
{
int a = 3, b = 8;
// 常量指针的语法:const 数据类型* 变量名;
// 不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
const int* p = &a;
a = 13;
cout << "a=" << a << ",*p=" << *p << endl;
p = &b;
cout << "b=" << b << ",*p=" << *p << endl;
int* p2 = &a;
*p2 = 100;
cout<<a<<endl;
// 指针常量语法:数据类型* const 变量名;
// 指向的变量(对象)不可改变;在定义的同时必须初始化;可以通过解引用的方法修改内存地址中的值。
// int* const p=&a;
// *p = 13;
// cout << "a=" << a << ",*p=" << *p << endl;
//int bh = 3; // 超女的编号。
//string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
//
//func(&bh, &message); // 调用向超女表白的函数。
//cout << "亲爱的" << bh << "号:" << message << endl;
}
- 指向的变量(对象)可以改变(之前是指向变量a的,后来可以改为指向变量b)。
- 一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值。
- 如果用于形参,虽然指向的对象可以改变,但这么做没有任何意义。
- 如果形参的值不需要改变,建议加上const修饰,程序可读性更好。
指针常量
指向的变量(对象)不可改变。
- 在定义的同时必须初始化,否则没有意义。
- 可以通过解引用的方法修改内存地址中的值。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(const int *no, const string *str) // 向超女表白的函数。
{
// *no = 8;
// *str = "我有一只小小鸟。";
cout << "亲爱的" << *no << "号:" << *str << endl;
}
int main()
{
int a = 3, b = 8;
// 常量指针的语法:const 数据类型* 变量名;
// 不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
// const int* p = &a;
// a = 13;
// cout << "a=" << a << ",*p=" << *p << endl;
// p = &b;
// cout << "b=" << b << ",*p=" << *p << endl;
// int* p2 = &a;
// *p2 = 100;
// cout<<a<<endl;
// 指针常量语法:数据类型* const 变量名;
// 指向的变量(对象)不可改变;在定义的同时必须初始化;可以通过解引用的方法修改内存地址中的值。
int* const p=&a;
*p = 13;
cout << "a=" << a << ",*p=" << *p << endl;
//int bh = 3; // 超女的编号。
//string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
//
//func(&bh, &message); // 调用向超女表白的函数。
//cout << "亲爱的" << bh << "号:" << message << endl;
}
常指针常量
指向的变量(对象)不可改变,不能通过解引用的方法修改内存地址中的值。(结合前面俩个)
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(int* const no, const string* const str) // 向超女表白的函数。
{
cout << "亲爱的" << *no << "号:" << *str << endl;
*no = 8;
//*str = "我有一只小小鸟。";
}
int main()
{
int a = 3, b = 8;
// 常量指针的语法:const 数据类型* 变量名;
// 不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
// const int* p = &a;
// a = 13;
// cout << "a=" << a << ",*p=" << *p << endl;
// p = &b;
// cout << "b=" << b << ",*p=" << *p << endl;
// int* p2 = &a;
// *p2 = 100;
// cout<<a<<endl;
// 指针常量语法:数据类型* const 变量名;
// 指向的变量(对象)不可改变;在定义的同时必须初始化;可以通过解引用的方法修改内存地址中的值。
// int* const p=&a;
// *p = 13;
// cout << "a=" << a << ",*p=" << *p << endl;
int bh = 3; // 超女的编号。
string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
func(&bh, &message); // 调用向超女表白的函数。
cout << "亲爱的" << bh << "号:" << message << endl;
}
void *
表示接受任意数据类型的指针
- 不能对void *指针直接解引用(需要转换成其它类型的指针)。
- 把其它类型的指针赋值给void*指针不需要转换。
- 把void *指针赋值给把其它类型的指针需要转换。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
// 只关心地址本身,不关心里面的内容,用void *可以存放任意类型的地址。
// 显示变量的十六进制地址的函数:varname-变量名,p-变量的地址。
void func(string varname, void* p)
{
cout << varname<< "的地址是:" << (long long)p << endl;
cout << varname << "的值是:" << *(char *)p << endl;
}
int main()
{
int a=89;
char b='X';
cout << "a的地址是:" << (long long)& a << endl;
cout << "b的地址是:" << (long long)& b << endl;
func("a", &a);
func("b", & b);
}
取值的时候得加 (char ),
就是把原本的void类型的指针转换为字符类型的指针,再解引用就可以取值了
c++内存模型
内存主要分成四个区,分别是
- 栈:存储局部变量、函数参数和返回值
- 堆:存储动态开辟内存的变量。
- 数据段:存储全局变量和静态变量。
- 代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改。
-
栈和堆的主要区别
1)管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中不释放,程序结束时由操作系统回收。
2)空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有8M(可以修改系统参数)。
3)分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)。
4)分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门的指令,效率比较高;堆是由C++函数库提供的。
5)是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降。
6)增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。
动态分配内存new 和delete
使用堆区的内存有四个步骤:
1)声明一个指针;
2)用new运算符向系统申请一块内存,让指针指向这块内存;
3)通过对指针解引用的方法,像使用变量一样使用这块内存;
4)如果这块内存不用了,用delete运算符释放它。
- 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。
- 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。
- 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收。
- 就算指针的作用域已失效,所指向的内存也不会释放。
- 用指针跟踪已分配的内存时,不能跟丢。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
// 1)声明一个指针;
// 2)用new运算符向系统申请一块内存,让指针指向这块内存;
// 3)通过对指针解引用的方法,像使用变量一样使用这块内存;
// 4)如果这块内存不用了,用delete运算符释放它。
// 申请内存的语法:new 数据类型(初始值); // C++11支持{}
// 释放内存的语法:delete 地址;
int* p = new int(5);
cout << "*p=" << *p << endl;
*p = 8;
cout << "*p=" << *p << endl;
delete p;
// for (int ii = 1; ii > 0; ii++)
// {
// int* p = new int[100000]; // 一次申请100000个整数,这个语法以后再讲。
// cout << "ii="<<ii<<",p=" << p << endl;
// }
}
二级指针
指针是指针变量的简称,也是变量,是变量就有地址。
指针用于存放普通变量的地址。
二级指针用于存放指针变量的地址。
使用指针有两个目的:1)传递地址;2)存放动态分配的内存的地址。
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参用二级指针。
把普通变量的地址传入函数后可以在函数中修改变量的值;把指针的地址传入函数后可以在函数中修改指针的值。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(int **pp)
{
*pp = new int(3);
cout << "pp=" << pp << ",*pp=" << *pp << endl;
}
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;
cout << "**ppii=" << **ppii << endl;
cout<<"--------------"<<endl;
int* p=0;
func(&p);
/*{
int** pp = &p;
*pp = new int(3);
cout << "pp=" << pp << ",*pp=" << *pp << endl;
}*/
cout << "p=" << p << ",*p=" << *p << endl;
}
空指针
在C和C++中,用0或NULL都可以表示空指针。
声明指针后,在赋值之前,让它指向空,表示没有指向任何地址。
如果对空指针解引用,程序会崩溃。(访问空指针)
如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。
在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int* p= 0;
cout<<"p="<<p<<"*p="<<*p<<endl;
// delete p;
// cout<<"成功删除空指针"<<endl;
return 0;
}
NULL指针分配的分区:其范围是从 0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区
直接对空指针解引用,报错。。
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(int* no, string* str) // 向超女表白的函数。
{
//if ((no == 0) || (str == 0)) return;
cout << "亲爱的" << *no << "号:" << *str << endl;
}
int main()
{
// int bh = 3; // 超女的编号。
// string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
int* bh = 0; // new int(3);
string* message = 0; // new string("我是一只傻傻鸟。");
func(bh,message); // 调用向超女表白的函数。(就是指针的拷贝版,不能修改主函数中的变量值)
delete bh; delete message;
}
加入判断形参是否为空指针的代码
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(int* no, string* str) // 向超女表白的函数。
{
if ((no == 0) || (str == 0)) return;
cout << "亲爱的" << *no << "号:" << *str << endl;
}
int main()
{
// int bh = 3; // 超女的编号。
// string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
int* bh = 0; // new int(3);
string* message = 0; // new string("我是一只傻傻鸟。");
func(bh,message); // 调用向超女表白的函数。(就是指针的拷贝版,不能修改主函数中的变量值)
delete bh; delete message;
}
或者,传递地址给形式参数
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(int** no, string** str) // 向超女表白的函数。
{
//if ((no == 0) || (str == 0)) return;
cout << "亲爱的" << *no << "号:" << *str << endl;
}
int main()
{
// int bh = 3; // 超女的编号。
// string message = "我是一只傻傻鸟。"; // 向超女表白的内容。
int* bh = 0; // new int(3);
string* message = 0; // new string("我是一只傻傻鸟。");
func(&bh,&message); // 调用向超女表白的函数。(就是指针的拷贝版,不能修改主函数中的变量值)
delete bh; delete message;
}
用0和NULL表示空指针会产生歧义,C++11建议用nullptr表示空指针,也就是(void *)0。(原因是和int类型重载)
#include<stdio.h>
void func(char* p)
{
printf("第一个char*\n");
}
void func(int x)
{
printf("第二个char*\n");
}
int main()
{
func(NULL);
return 0;
}
或者这个例子
#include<iostream>
using namespace std;
void func(char* p)
{
cout << " char*" << endl;
}
void func(int x)
{
cout << "int x" << endl;
}
int main()
{
func(NULL);
return 0;
}
同样的程序,在c语言中运行程序结果如我们所料是char * ,但在c++中程序运行打印的结果却是int x
参考博文
在C++11标准中,nullptr是一个所谓"指针空值类型"的常量。
在书写C++11代码时将NULL替换为nullptr可以使我们获得更加健壮的代码
#include<iostream>
using namespace std;
void func(char* p)
{
cout << "char*" << endl;
}
void func(int x)
{
cout << "int x" << endl;
}
int main()
{
func(nullptr);
func(0);
func(NULL);
return 0;
}
野指针
野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。
出现野指针的情况主要有三种:
- 指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。
- 如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。
- 指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋给了指针。
如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度
#include<iostream>
using namespace std;
int main()
{
int* p = (int*)0x0099999222;
cout<<"p="<<p<<"*p="<<*p<<endl;
return 0;
}
#include<iostream>
using namespace std;
int main()
{
int* p = new int(3);
cout<<"p="<<p<<"*p="<<*p<<endl;
delete p;
cout<<"p="<<p<<"*p="<<*p<<endl;
return 0;
}
#include<iostream>
using namespace std;
int* func(){
int a = 3;
cout<<"a="<<a<<"&a="<<&a<<endl;
return &a;
}
int main()
{
int* p =func();
cout<<"p="<<p<<"*p="<<*p<<endl;
return 0;
}
规避方法:
- 指针在定义的时候,如果没地方指,就初始化为nullptr。
- 动态分配的内存被释放后,将其置为nullptr。
- 函数不要返回局部变量的地址。
函数指针
函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其它函数
函数指针使用方法:
a)声明函数指针;
b)让函数指针指向函数的地址;
c)通过函数指针调用函数。
- 声明函数指针
- 赋值
- 函数指针调用函数
#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语言
}
#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); // 张三要表白。
cout<<"------------------"<<endl;
show(ls, 4); // 李四要表白。
}