指针与引用详解,C++之秀出不一样的风采

    xiaoxiao2024-12-25  13

    指针与引用 指针

    每个类型都有对应的指针,如

    <span style="font-size:18px;">int* ip;//指针的类型为int* , 而指针所指向的类型为int char* cp; float* fp; double* dp; int (*P)[4]; </span> 定义中指针的*可以居左,居中,居右。由于指针本身也是一种类型,甚至指针本身也有对应的指针类型: <span style="font-size:18px;">int** iip; //即(int*)* iip;</span> iip称为二级整型指针变量。一个*只能修饰一个指针: <span style="font-size:18px;">int* ip, iq;</span> 则表示ip为指针变量,iq为整型变量 <span style="font-size:18px;">int* ip, *iq; //两者均为指针变量</span> 指针变量的定义,由数据类型后跟星号,再跟指针变量名组成。指针变量在不引起混淆的情况下也称为指针

    指针的赋值:

    <span style="font-size:18px;">int* ip, ic = 8; int* iq = ⁣&ic //初始化 ip = ⁣&ic; //赋值</span> "&"表示实体的地址,表示取地址操作。由于字面值不认为是具有空间地址的实体,不能进行&操作: <span style="font-size:18px;">int* iq = &18;//错误,后面会提到_cast操作,转化赋值</span> 由于指针本身也是具有空间地址的实体,因此也可以被别的指针(二级指针)所操纵: <span style="font-size:18px;">int ic = 18; int* ip = ⁣&ic; int** iip = &ip; cout << **iip << endl; //输出18</span> 指针的0值不是指向地址为0的空间,而是表示空指针,即不指向任何空间。而指针只有指向具体的实体才使得间访操作变得有意义: <span style="font-size:18px;">int* ip; *ip = 18; //错</span> 从另一个角度说,指针忘了赋值,比整型变量忘了赋值危险的多,因为这种错误不能被编译所发现,到了运行时发现可能就晚了

    指针是有类型的。给指针赋值,不但必须是一个地址,而且应该是与指针相符的变量或常量的地址。一般来说,指针的类型不同,是不能相互赋值的:

    <span style="font-size:18px;">float f = 18.0f; int* ip = &f;//错,类型不同</span> 但从地址的本质来说,无非是二进制表示的整数而已,更何况计算机的内存空间实体是没有类型的,都是二进制位码,只是不同类型的解读方式不同罢了,因此这样看来,不同类型的指针又是统一的,都是二进制的地址。因此可以通过蛮横的强制转换(cast)操作: <span style="font-size:18px;">int* ip = (int*)&f;</span> (int*)就特别强势了,表明该地址的空间不管放什么数据,都按照整型地址来看待,即使放的是函数代码,这就容易导致程序的运行崩溃了

    既然这样,那为什么还要有转换这种机制呢?

    转换操作的目的一是要逃避编译的类型检查,让本来不是专门用来完成本任务的模块也可以凑合着用一用;二是希望以一种类型的角度来理解另一种类型。

    <span style="font-size:18px;">int* ip = reinterpret_cast<int*>(&f);//两种类型地址的转换</span> 指针运算:

    指针值是一个内存地址,它的内部表示为整数,指针变量所占的空间大小总是等同于整型变量的大小,可以通过sizeof()测算,32位平台中,指针本身占据了4个字节的长度,指针与整型的操作规则不同,如两个指针值不能相加,两个指针值相减得到一个整型数,指针加上或减去一个整数得到另一个指针值等等

    <span style="font-size:18px;">int* ip = 18;//错误 int* iq = reinterpret_cast<int*>(18);//ok</span> 指针的加减整型数大多用在数组这种连续又是同类型元素的序列空间中,因为数组名本身就是代表元素类型的地址,可以把数组的起始地址赋值给指针,通过指针加减整数来对数组元素进行操作 <span style="font-size:18px;">int array[6] = {1,2,3,4,5,6}; for(int* ip=array; ip<array+6; ip++)//地址递增 cout << *ip << endl;</span> 需要注意的是,指针的增减是以该类型的实体大小为单位的,这里的int类型指针每次加1,实际增加了4个字节,如果是double的话,每次加1,就是8个字节,对了,另一点要注意,约束指针,不要让指针获得数组首地址后,进行增减超过了数组的地址,这会出问题的!

    指针可以操作指针值(地址)和间访值(指向的实体),同时也有了指针常量和常量指针:

    <span style="font-size:18px;">int a = 18, c = 22; const int* ip = &a;//常量指针,等于int const* ip = &a; int* const cp = &a;//指针常量 const int* const icp = &a;//常量指针常量 ip = &c;//ok,而*ip = 81;则错 *cp = 81;//ok,而cp = &c;则错 icp =&c;//错,而*icp = 81也错 </span>

    常量指针,我理解为指向常量的指针,既然指向的是常量,那对应的值虽不能改变,但指向可以变(地址可以变),

    同理,指针常量,就是说这个指针是常量,指向不能变(地址不变),但地址里面存放的内容(值)可以变

    const只是一个限定指针的操作,并不能限定空间上实体的可改变性,如

    <span style="font-size:18px;">int a = 18; const int* ip =&a; a =81; cout << a;//输出81</span>

    函数指针:

    函数都有自己的内存单元,有自己的起始地址,而函数指针就是指向函数入口起始地址的指针

    int max(int x, int y); int (p*)(int a, int b); p = max;//max的起始地址赋给指针变量p p它指向max函数,需要注意类型的相匹配

    引用

    引用就是别名,当建立引用时,用一个具体类型的实体去初始化别名,这便让引用关联了其实体变量(或对象),这时,引用代表的就是那个实体,引用所做出的改变,相应的实体也会发生改变

    <span style="font-size:18px;">int a = 5; int& b = a;//"&"可以居左,居中或居右 b = 20; cout << a;//输出20 </span> 引用定义必须初始化(必须要有关联的实体),这是引用与指针的不同之处,给引用初始化的总是一个内存实体,引用也要求类型的严格匹配,一旦建立了引用,其值可以改变,但其地址值却不会改变,这就确定了它与一个实体的联系(引用是一个隐性的指针),这种联系是打不破的,直到引用自身的灭亡。即:引用值是引自所指向的实体。

    引用中&操作符与C中取地址运算不同,它是起标识作用

    引用往往用于函数参数中:

    <span style="font-size:18px;">#include<iostream> using namespace std; //交换函数--------------------- void swap(int& x1, int& x2){ int t = x1; x1 = x2; x2 = t; } int main(){ int a, b; cin >> a >> b; swap(a,b); cout << a << ' ' << b <<endl; }</span> 可以看出引用可以实现与传递指针相同的结果,引用所做出的改变就是对原实体的改变,而且,使用引用传递参数,内存中不会产生额外的内存申请(似乎与不同的编译器是否将引用转化成指针有关,或许也会占一些内存吧,这个原理就不大懂了),它是直接对原实参做出的改变,如果参数传递的数据比较大时,建议用引用比较好,用引用比一般变量传递的效率和所占空间都好

    若想利用引用来提高效率,又想保护传来的数据不在函数中改变,可以使用常引用

    常引用:

    引用型参数如果能用const,就尽量用const,反例:

    <span style="font-size:18px;">string f(); void g(string); int main(){ //下面两个表达式是非法的 g(f());//错 g("Hello");//错 }</span>

    原因是因为f()和"Hello"参数在传递时,都会产生一个临时对象,而C++中这些临时对象都是const类型的,因此上面的函数调用试图将const类型的对象转换为非const类型,是非法的

    参数传递中的引用:

    对传递来的数据或对象进行一些列的操作,而不改变传来的数据或对象

    void fun(const string& s){ ... } fun("Hello"); 函数返回值中的引用:

    一般用在类成员函数中用来返回类对象或函数中来返回相应类型的引用对象

    class Vehicle{ ... const Vehicle& get(){return mCar}; ... } //.............. const string& s(const string& s1, const string& s2){ return s1.size<s2.size? s1 : s2; }//引入const后返回的值不能修改,去掉const则可以

    相比较而言,指针没有引用直观,对指针而言,可以为null,但引用不可以,也就没有所谓的null检查,但就本质而言,引用背后的实现方式还是指针...

    转载请注明原文地址: https://ju.6miu.com/read-1294988.html
    最新回复(0)