C++学习d2---指针

目录

指针的基本概念

1.变量的地址

2.指针变量

3.对指针赋值:

4.指针占用的内存

使用指针

用const修饰的指针

1.常量指针

2.指针常量

3.常指针常量

void关键字

1.函数返回值用void,表示函数无返回值

2.函数形参填void,表示函数不需要参数:

3.函数的形参用void*,表示接受任意数据类型的指针

C++内存模型:

动态分配内存 new 和 delete

二级指针

空指针

注意:

c++ 11 的nullptr

野指针

函数指针:

如果有错误,请指出!

指针的基本概念

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 = &ii; 
    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);          // 李四要表白。
}


如果有错误,请指出!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值