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;
    
}

指针变量

专用于存放变量在内存中的起始地址

指针变量 简称 指针

对指针(指针变量)赋值

  1. 对指针的赋值操作也通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”。
  2. 管是整型、浮点型、字符型,还是其它的数据类型的变量,它的地址都是一个十六进制数
#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;
}

  1. 指向的变量(对象)可以改变(之前是指向变量a的,后来可以改为指向变量b)。
  2. 一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值。
  3. 如果用于形参,虽然指向的对象可以改变,但这么做没有任何意义。
  4. 如果形参的值不需要改变,建议加上const修饰,程序可读性更好。

指针常量

指向的变量(对象)不可改变。

  1. 在定义的同时必须初始化,否则没有意义。
  2. 可以通过解引用的方法修改内存地址中的值。
#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 *

表示接受任意数据类型的指针

  1. 不能对void *指针直接解引用(需要转换成其它类型的指针)。
  2. 把其它类型的指针赋值给void*指针不需要转换。
  3. 把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 = &ii;        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;
}

野指针

野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。

出现野指针的情况主要有三种:

  1. 指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。
  2. 如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。
  3. 指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋给了指针。

如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度

#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;
}

规避方法:

  1. 指针在定义的时候,如果没地方指,就初始化为nullptr。
  2. 动态分配的内存被释放后,将其置为nullptr。
  3. 函数不要返回局部变量的地址。

函数指针

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

函数指针使用方法:

a)声明函数指针;
b)让函数指针指向函数的地址;
c)通过函数指针调用函数。

  1. 声明函数指针
    在这里插入图片描述
  2. 赋值
    在这里插入图片描述
  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语言
}
#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);          // 李四要表白。
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值