关于c++:我整理了这篇指针的知识点想必对你有用

7次阅读

共计 5310 个字符,预计需要花费 14 分钟才能阅读完成。

指针和援用的区别

  1. 非空区别: 任何状况下都不能应用指向空值的援用,一个援用必须总是指向某些对象。指针能够指向空。
  2. 合法性区别: 援用在应用之前不须要测试合法性,指针应该总是要被测试,避免其为空
  3. 可修改性区别: 援用在初始化时指定对象,当前不能批改。

指针传递动态内存

例 1: 程序测试后会有什么后果?

#include<iostream>
#include<cstring>
using namespace std;

void getMemory(char*p, int num)
{p = (char*)malloc(sizeof(char) * num);
}

int main(int argc, const char* argv[])
{
    char *str = NULL;
    getMemory(str, 100);
    strcpy(str, "hello");
    return 0;
}

问题呈现在 getMemory 里,编译器总是为函数的每个参数制作一个长期正本。在本题中,p 为 str 的拷贝,p 申请了一个新的内存空间,然而并没有影响到 str,str 还是 NULL,再调用 strcpy(), 则会使代码解体,并且 p 申请的内存也始终没用开释,造成内存泄露。

正确的办法是把往 getMemory 内传入 str 的地址。

#include<iostream>
#include<cstring>
using namespace std;

void getMemory(char**p, int num)
{*p = (char*)malloc(sizeof(char) * num);
}

int main(int argc, const char* argv[])
{
    char *str = NULL;
    getMemory(&str, 100);
    strcpy(str, "hello");
    cout << str << endl;
    return 0;
}

不过这样写有些麻烦,咱们能够间接把申请好的内存返回。

#include<iostream>
#include<cstring>
using namespace std;

char* getMemory(int num)
{return (char*)malloc(sizeof(char) * num);
}

int main(int argc, const char* argv[])
{
    char *str = NULL;
    str = getMemory(100);
    strcpy(str, "hello");
    cout << str << endl;
    return 0;
}

例 2: 这个函数有什么问题?

char* strA()
{char str[] = "hello world";
    return str;
}

str 里存的地址是函数 strA 栈帧里 "hello world" 的首地址,函数调用实现,长期空间被重置。

如果要取得正确的函数,能够这样写:

char* strA()
{
    char *str = "hello world";
    return str;
}

首先要搞清楚char str[], char* str,

char str[] 调配的是一个部分数组,

char* str 调配的是一个指针遍历,

部分数组是局部变量,它对应的是内存中的栈。指针变量是全局变量,它对应的是内存中的全局区域。

不过上述代码只能 c 语言这么写,c++ 不容许

ISO C++ forbids converting a string constant to‘char*’

能够这么写:

char* strA()
{static char str[] = "hello world";
    return str;
}

例 3: 上面代码的输入后果是什么?

#include<iostream>
using namespace std;

class A
{
public:
    A() { m_a = 1; m_b = 2;}
    ~A(){}
    void fun() { printf("%d%d", m_a, m_b);}
private:
    int m_a;
    int m_b;
};

class B
{
public:
    B() {m_c = 3;}
    ~B();
    void fun() {printf("%d", m_c);}
private:
    int m_c;
};


int main(int argc, const char* argv[])
{
    A a;
    B *pb = (B*)(&a);
    pb->fun();
    return 0;
}

这道题的目标就是考查你对内存偏移的了解,

B* pb = (B*)(&a);, 这是一个横蛮的转换,强制把 a 地址内容看成一个 B 类的对象,pb 指向的是 a 类的内存空间, 把 a 类空间依照 B 类的构造来解读。

函数指针

例 1: 找出下列程序的谬误

#include<iostream>
using namespace std;

int Max(int x, int y)
{return x > y ? x : y;}

int main(int argc, char const *argv[])
{
    int *p = &Max;
    cout << p(2, 1) << endl;
    return 0;
}

这道程序提存在着函数指针的谬误应用问题,正确的写法为: int (*p)(int, int) = &Max;

int *p p 是 int 型的指针

int *p(int, int), p 是一个函数,返回值为int*

int (*p)(int, int), p 是一个指针,指向函数的地址,函数的返回值为 int

例 2: 上面的数据申明都代表什么?

float(**def)[10];
double*(*gh)[10];
double(*f[10])();
int*((*b)[10]);
long(*fun)(int)
int(*(*F)(int, int))(int)

答案如下:

float(**def)[10];  // def 是二级指针,指向一级指针,一级指针指向数组,数组的大小为 10,数组元素类型为 float
double*(*gh)[10];  // gh 是一级指针,指向一个数组,数组大小为 10,数组元素的类型为 double*
double(*f[10])();  // f 是一个数组,数组大小为 10,数组的元素类型为指针,指针指向的类型为 double() 的函数
int*((*b)[10]);  // b 是一个指针,指向一个数组,数组的大小为 10,数组元素的类型为 int*
long(*fun)(int)  // fun 是一个函数指针,指向 long(int) 型的函数
int(*(*F)(int, int))(int)  // F 是一个指针,指向一个函数,函数的参数为(int, int), 函数的返回值是一个指针,指向一个函数,函数的参数为(int), 函数的返回值为 int

指针数组和数组指针

例 1: 以下程序的输入是什么?

#include<iostream>
using namespace std;

int main(int argc, char const *argv[])
{int v[2][10] = {{1,2,3,4,5,6,7,8,9,10},
        {11,12,13,14,15,16,17,18,19,20},
    };
    int (*a)[10] = v;       // 数组指针是一个二级指针
    cout << a << endl;      // a 是一个指针,指向 {1,2,3,4,5,6,7,8,9,10}
    cout << *a << endl;     // * a 也是一个指针,指向 1 的地址
    cout << **a << endl;    // **a 取 1 的值

    cout << a + 1 << endl;  // 指针向后偏移一个地位,这个地位的长度为指针所指容量的大小,偏移后指向 {11,12,13,14,15,16,17,18,19,20}
    cout << *(a + 1) << endl;  // 和 * a 的原理是一样的,指向 11 的地址
    cout << **(a + 1) << endl;  // 取 11 的值

    cout << *a + 1 << endl;  // * a 指向 1 的地址,*a + 1, 指针向后偏移一个地位,* a 指向的是 int 型的数据 m, 向后偏移 sizeof(int),指向 2
    cout << *(*a+1) << endl;  // 取 2
    return 0;
}

例 2: 用变量 a 给出上面的定义

  1. 一个整型数。
  2. 一个指向整型数的指针。
  3. 一个指向指针的指针,它指向的指针是一个指向一个整型数
  4. 一个有 10 个整型数的数组
  5. 一个有 10 个指针的数组,该指针是指向一个整型数的
  6. 一个指向有 10 个整型数数组的指针
  7. 一个指向函数的指针,该函数有一个整型参数并返回一个整型数
  8. 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数

答案:

// 1
int a;
// 2
int *a;
// 3
int **a;
// 4
int a[10];
// 5
int *a[10];
// 6
int (*a)[10];
// 7
int (*a)(int);
// 8
int (*a[10])(int);

例 3: 写出如下程序片段的输入

int a[] = {1,2,3,4,5};
int *ptr = (int*)(&a + 1);
printf("%d %d", *(a+1), *(ptr - 1));

答案:

#include<iostream>
#include<cstdio>
using namespace std;

int main(int argc, char const *argv[])
{int a[] = {1,2,3,4,5};
    int *ptr = (int*)(&a + 1);

    cout << a << endl;  // 数组名的一个指向数组元素的常量指针
    cout << &a << endl;  // &a 并不是一个指向常量指针的指针,而是一个指向整个数组的指针

    // 以下两行验证以上的观点
    cout << a + 1 << endl;
    cout << &a + 1 << endl;

    // 所以 a + 1 指向 2,*(a+1) 为 2
    // &a + 1 应该指向 5 的下一个元素,ptr - 1 指向 5
    printf("%d %d", *(a+1), *(ptr - 1));
    return 0;
}

迷途指针和野指针

迷途指针: 指针指向一个内存,这个内存会回收了,然而没有对这个指针做解决,没有将指针设为空

野指针: 申明了一个指针,没有将指针初始化。

例 1: 上面的程序输入后果是什么?

#include<iostream>
using namespace std;

int main(int argc, char const *argv[])
{char s1[] = "hello";
    char s2[] = "the";
    char s3[] = "world";
    char* a[] = {s1, s2, s3};
    char **pa = a;
    pa++;
    cout << *pa << endl;
    return 0;
}

a 是一个常量指针,指向数组的首地址,pa++, 向后挪一个指针大小,指向 s2, 输入 “the”

指针和句柄

句柄是一个整数,是操作系统在内存中保护的一个对象,内存物理地址列表的整数索引,因为操作系统在内存治理时常常会将以后闲暇对象的内存开释掉,当须要拜访时再从新提交到物理内存,所以对象的物理地址是变动的,不容许程序间接通过物理地址来拜访对象。程序将想拜访的对象的句柄传递给零碎,零碎依据句柄检索本人保护的对象列表就能晓得程序想拜访的对象及其物理地址了。

句柄是一种指向指针的指针。咱们晓得,所谓指针是一种内存地址,应用程序启动后,组成这个程序的各对象是驻留在内存的。如果简略的了解,仿佛咱们只有晓得内存的首地址,那么就能够随时用这个地址拜访对象。然而,如果真的这样认为,那么就大错特错了,咱们晓得,操作系统是一个以虚拟内存为根底的,存在换页景象,对象呗挪动意味着它的地址变动了,如果地址总是变动,咱们该怎么寻找对象呢?为了解决这个问题,操作系统为各应用程序腾出一些内存地址,用来专门注销各利用对象在内存中的地址变动,而注销的地址是不变的,操作系统挪动对象后,将对象的地址通知给句柄,通过句柄就能晓得对象具体的地位了。

句柄 –> 注销对象地址的地址 –> 对象的地址 –> 对象

程序每次重新启动,零碎不保障调配给这个程序的句柄还是原来的句柄,就好比去电影院每次卖给咱们的都不是同一个座位。

this 指针

对于 this 指针,有这样一段形容: 当你进入一个房子后,你能够看见桌子,椅子,等,然而你看不到全貌了

对于一个类的实例来说,你能够看到成员函数,成员变量,然而实例自身呢?this 指针就是这样一个指针,时时刻刻指向实例自身

  1. this 指针实质是一个函数参数,只是编译器暗藏起来的,语法层面的参数,实际上,成员函数默认第一个参数为 T* const this
  2. this 在成员函数的开始前结构,完结后革除。

a.func(10); 会被编译器编译成 A::func(&a, 10);, 看起来和动态函数没区别,不过,区别还是有的。编译器通常会对 this 指针做一些优化,this 指针的传递效率比拟高,如 VC 通常是通过 ecx 寄存器传递。

  1. this 指针并不占用对象空间,this 相当于非动态成员函数的一个隐含的参数,不占对象空间。
  2. this 指针寄存在何处?this 指针会因编译器不同而有不同的地位,可能是堆,栈,也可能是寄存器。
  3. this 指针是如何传递给类中的函数的?大多数编译器是通过 ecx 寄存器传递 this 指针,事实上,这也是一个潜规则,一半来说,不同编译器都会听从统一的传参准则,否则不同的编译器产生的 obj 就无奈匹配了
  4. 咱们只有取得一个对象后,能力通过对象应用 this 指针,如果咱们晓得对象 this 的地位,能够间接应用吗?

this 指针只有在成员函数中才有定义。因而,你取得一个对象后,也不能通过对象应用 this 指针,只有在成员函数内才有 this

正文完
 0