【基础复习】三:指针与引用


指针基本问题

  • 指针和引用的差别
    • 非空区别。在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让他指向一个对象,但是该变量在某些时候也可能不指向任何对象,这是你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针要高。
    • 合法性区别。在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
    • 可修改区别。指针与引用的另一个重要的区别是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被制定的对象,以后不能改变,但是指定的对象其内容可以改变。
    • 应用区别。总的来说,在以下情况下应该使用指针:一是考虑到存在不指向任何对象的可能(在这种情况下,能够设置指针为空),二是需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,就应该使用引用。

传递动态内存

1.错误示例:

#include<iostream>
void GetMemory(char* p, int num) {
    p = (char*)malloc(sizeof(char)*num);  //此时的p只是函数栈中的临时副本
}
int main() {
    char* str = NULL;
    GetMemory(str, 100);
    strcpy(str, "hello");//此时str仍然为NULL
    return 0;
}

正确示例1:使用指针的指针

#include<iostream>
void GetMemory(char**p, int num) {
    *p = (char*)malloc(sizeof(char)*num);
}
int main() {
    char *str=NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    cout << *str << endl;
    cout << str << endl;
    cout << &str << endl;
    return 0;
}

正确示例2:使用返回值

#include<iostream>
char* GetMemory(char*p, int num) {
    p = (char*)malloc(sizeof(char)*num);
    return p;
}
int main() {
    char *str = NULL;
    str = GetMemory(str, 100);
    strcpy(str, "hello");
    cout << *str << endl;   //输出首字符, ‘h’
    cout << str << endl;    //输出字符串,“hello”
    cout << &str << endl;   //输出字符串地址
}

正确示例3:使用指针的引用(前面两种是书上的,但是这种也可以,而且传引用似乎开销更小)

#include<iostream>
void GetMemory(char* &p, int num) {
    p = (char*)malloc(sizeof(char)*num);  //此时的p只是函数栈中的临时副本
}
int main() {
    char* str = NULL;
    GetMemory(str, 100);
    strcpy(str, "hello");//此时str仍然为NULL
    return 0;
}

2.下面函数有什么问题? ```c++ char* strA() { char str[] = "hello world"; return str; } ```

解析:
因为这个函数返回的是局部变量的地址,当调用这个函数后,这个局部变量str就释放了,所以返回的结果是不确定的且不安全,随时都有被收回的可能。
首先要复习一下C程序的内存布局。那么这里的str里存放的是函数strA栈帧里“hello world”数组的首地址,函数调用结束后栈帧恢复到调用strA之前的状态,临时空间被重置,strA栈帧不再属于可访问的范围,存在于strA中的局部变量——“hello world”数组自然也不能访问了。
可以考虑改成以下:

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

此时,“hello world”是作为一个字符串常量存在于数据段中的,而不是在strA的函数栈中,此时返回一个指针,指针指向数据段中(只读区)的这个字符串常量的位置,自然就可以成功访问。而这里,试图修改str是不合法的,如*str='n'会报错。
也可以使用static开辟一段静态存储空间:

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

补充:感觉关于内存分布的部分,还是这一篇讲的比较完整:c++内存分布!!!

3.下面程序的输出结果?

#include<cstdio>

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() {
    A a;
    B *pb = (B*)(&a);
    pb->fun();
}

解释:
输出结果为1.
强行把A类的内存当做B来处理。pb->fun()调用的是B::fun()来打印m_c。而m_c是B类对象的唯一元素,对应的也就是A类内存中存放的第一个元素,也就是m_a。所以打印了1.


4.下面程序的输出? ```c++ #include #include using namespace std;

class A
{
public:
int a;
A(){a=1;}
void print() {printf("%d", a);}
};

class B: public A
{
public:
int a;
B() {a=2;}
};

int main() {
B b;
b.print();
printf("%d", b.a);
}

**解析:**
B类中的a把A类中的a“隐藏”了。在构造B类时,先调用A类的构造函数。所以A类的a是1,B类的a是2.输出是12
<br>
- `const char * const * keyword1` keyword1是一个二级指针变量,这个二级指针指向一个一级指针常量(不能通过修改这个二级指针去修改一级指针中的值),这个一级指针常量指向一个字符常量(不能通过这个一级指针修改这个字符的值)
- `const char *keyword2` keyword2是一个指针变量,指向一个只读(不可通过该指针修改)的字符 
- `const char * const keyword3` keyword3是一个指针常量,指向一个只读(不可通过该指针修改)字符
- `const char keyword4`  keyword4是一个字符常量

ps:主要就是记住*是和右边结合的,const和*的位置关系就可以知道这个变量究竟是什么了.const总的来说其实只是从编译的语法语义上去进行限定以便更好的编程,实际上还是可以通过其他方式修改变量的值。

####函数指针
1.示例程序:
```c++
#include<cstdio>

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

int main() {
    int (*p)(int, int) = &max;
    int a, b, c, d;
    scanf("%d%d%d", &a, &b, &c);
    d = (*p)((*p)(a, b), c);
    printf("%d
", d);
}
  • float(**def)[10] def是一个二级指针,指向一个一级指针,这个一级指针指向一个float数组
  • double*(*gh)[10] gh是一个一级指针,指向一个double*数组
  • double(*f[10])() f是一个数组,数组的元素是函数指针,函数形参为空,返回值为double
  • int*((*b)[10]) b是一个指针,指向一个数组,数组元素是int*
  • long (*fun)(int) fun是一个函数指针,函数返回值是long,参数是一个int
  • int (*(*F)(int, int))(int) F是一个函数指针,该函数的参数是(int,int),返回值是一个函数指针,这个返回的函数指针参数是一个int,返回值是一个int

ps:[]的优先级比*高

指针数组和数组指针

1.下面程序的输出?

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

int main() {
    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;
    cout << **(a+1) << endl;
    cout << *(*a+1) << endl;
    cout << *(a[0]+1) << endl;
    cout << *(a[1]) << endl;
}

解析:
1 11 2 2 11
a是一个指向10个int数组的指针,因此a+1表明指针向后移动1*sizeof(数组大小)


2.下面程序的输出? ```c++ #include #include using namespace std;

int main() {
int a[]={1,2,3,4,5};
int ptr = (int)(&a+1);
cout << *(a+1) << " " << *(ptr-1) << endl;
}

**解析:**
2 5
a是一个5个元素的数组,(&a+1)是5元素数组的下一个数组的首地址,也就是'5'这个元素后面的一个位置,所以*(ptr-1)的值就是5了。

####迷途指针
- [空指针、迷途指针、野指针](http://www.cnblogs.com/thinknothing/p/3947720.html)
- [百度笔试题:malloc/free与new/delete的区别](http://blog.csdn.net/hackbuteer1/article/details/6789164)

####指针和句柄
什么是句柄来着好难好难...我下次要去实验室拿回《c++ primer》重新看一下...
- [智能指针与句柄类(一)](http://www.cnblogs.com/Tour/p/4040517.html),[智能指针与句柄类(二)](http://www.cnblogs.com/Tour/p/4042106.html),[智能指针与句柄类(三)](http://www.cnblogs.com/Tour/p/4043887.html)
- [系统意义上的句柄](http://blog.csdn.net/newjerryj/article/details/4383701)
- [比较好的实践(关于句柄)](http://blog.chinaunix.net/uid-26275986-id-3848567.html)
- [其他一些资料](http://rangercyh.blog.51cto.com/1444712/1293679)
嗯...等看完书再回头看这些资料...

<br/>
- [c++ 智能指针用法详解](http://www.cnblogs.com/TenosDoIt/p/3456704.html)

####this指针
[IBM的面试题和解析](http://blog.csdn.net/wupenm/article/details/48090201)

<br>
<br>

-----
from《程序员面试宝典》
ps:怎么说呢,感觉书上很多的这些题目的结果都是针对32位机器?虽然题目没有指明不够严谨,但是熟悉计算机中的实际操作和存储一般也就知道实际结果了.

<br>
<br>
原文地址:https://www.cnblogs.com/zengyh-1900/p/5241625.html