我是荔园微风,作为一名在IT界整整25年的老兵,今天来聊聊Visual C++中*号的位置。

我知道在程序员队伍中有一群特别细心、谨慎的可爱的人,他们经常为一些在别人看来小的不能再小的问题所困惑。比如说,*号的位置,让很多人一直搞不清楚。

加上一些教程把*号印错了位置,引起很大的误解,再加上一些老程序员随意的很,一会把*号放前面,一会把*号放后面,让程序员们很困惑。

我今天这篇文章就来拯救一下细心、谨慎的程序员。

C/C++语言中*号的位置

C/C++语言中*的位置有如下两种书写方式:

int *a;  靠近变量

int* a;  靠近变量类型

两者意思相同且后者看上去更为清楚表明意思,也就是说a被声明为类型为 int* 的指针。

但是这样有时会有一些问题。原因如下:

int* b, c, d;

一般以为这条语句把所有三个变量声明为指向整形的指针, 但事实上并非如此。星号实际上只是表达式 *b 的一部分, 只对这个标识符有用。b 是一个指针, 但其余两个变量只是普通的整形。要声明三个指针, 正确的语句如下:

int *b, *c, *d;

但是,int *a[5]和int(*a)[5]是不一样的。

int *a[5]是指针数组,它的元素是整形指针

int(*a)[5]是指向整形数组的指针

在C/C++语言中,先定义了类型,后有的标识符,这些标识符组成的表达式,用于产生基本类型的变量
​例如 int *a;

​这条语句表示*a表达式产生的结果类型int ,知道*操作符的作用是间接访问操作,就可以知道a是指向int 的指针。

C/C++语言是很自由的语言。​知道编译机制,编译器编译时会将代码中的空格去除掉,链接最后转成二进制机器码,让机器可以识别,​所以有 ​int* a;这种定义形式。​可以看到这样比上面那个更清晰更容易看懂,a被声明为类型为int*类型的指针。

好,我们再来看C/C++语言中*号在不同使用环境下有不同的含义,对于急性子来说,现总结星号的含义有如下几种:

*代表乘法

作为算术运算符,*代表乘法,进行相乘运算

#include<stdio.h>

int main(void){

int a = 10;

int b = 20;

printf("%d",a*b);

return 0;

}

此例中*作为乘法使用。

*定义指针变量

int * p,定义了一个p变量,int *代表变量p是指针变量,只能存放变量地址。

#include<stdio.h>

int main(void){

int a = 100;

int* p;

p = &a;

printf("%p",p);

return 0;

}

此例中,定义了指针变量p,将变量a的地址存储在指针变量p中。

*解引用运算符

*作用是使用指针指向的变量值,引用为引用指针变量的地址,解引用为使用指针变量指向的值。该运算符放在指针变量的前面,表示以该指针变量内容为地址的变量。

如:int * p定义了指针变量p,则*p表示,以p内容为地址的变量

#include<stdio.h>

int main(void){

int a = 100;

int * p;

p = &a;

printf("%p\n",&a);

printf("%p\n",p);

printf("%d\n",*p);

return 0;

}

此例中定义了指针变量int * p,打印时&a与p结果均为变量a的地址,*p为以指针变量p内容为地址的变量,即为变量a的值。

对于急性子来说就是这样,但是实际上,*号的用法很多,有乘法运算符、复合赋值运算符、假读符、注释符、普通符号、指针定义符、指向运算符、行列地址转换符、地址值符。

我们来看代码。

#include <stdio.h>
 
int main()
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sum = 0;
 
 
    for (int i = 0; i < 10; i++)
    {   
        sum = *(*(&arr + 0) +  i);  
        printf("%d\n",sum);
    }
                                                                       
    printf("\n");
    return 0;
}

从上面代码可以看出,

arr是一个数组,arr表示的是数组首地址,那么&arr表示的就是首地址的地址,*(&arr+0)表示的就是首地址,*(&arr+0)+i得到的是第i个元素地址,那么*(* (&arr+0) + i)得到的就是第i个元素的值。
 

然后我们再综合一些&方面的知识,再看如下内容:

C/C++中*和&的用法详解

C++中*和&的用法在网上很多贴子里讲这些知识点多数都是分开讲其用法的,没有详细的总结,导致初学者在这方面的知识结构格外混乱,现在我们结合上面说的再深入一层。

C++语言是C语言的超集。几乎所有可以运行的C程序都是可以运行的C++程序。因此,写一个不包含C++特性的C++程序是可能的,尽管cout和引用的使用更好的构成了一个C++程序。C++和C在代码格式上偶尔会有不同。

C语言

∗号用法
乘法运算:x=y*z;
乘法赋值运算:x*=y;相当于x=x*y
注释:/*这里是你的注释*/
指针的声明:int *p 或 int* p; 读法:p是指向一个整数类型的指针。
复合指针: int **p; 或 int** p; 读法: p是一个指向一个指向整数类型的指针的指针。
解引用: x=*p 把指针p指向的值赋值给x。

&号用法
逻辑与:if((a>1)&&(b<0))
位运算与:x=a&b;
逻辑与赋值:x&=y;与 x=x&y含义相同
求地址运算符:p=&x;读法:把x的地址赋给p(指针)

代码详解
注:这里主要讲解关于指针与取地址的问题。

#include <stdio.h>

int main(){
    
int a = 10;
int *b = &a;

printf("%d\n", a);
printf("%d\n", &a);
printf("%d\n", b);
printf("%d\n", *b);
    
return 0;
}

运行结果:
10
6487572
6487572
10

变量a 本质上代表一个存储单元。CPU通过该存储单元的地址访问该存储单元中的数据。所以a本来代表两个值:存储单元的地址和储单元中的数据。C语言规定a表示存储单元中的数据,&a表示存储单元的地址。

a存储单元中的数据可以是一个普通数值,也可以是另一个存储单元的地址,比如:a = &b;语句就是将b的存储单元的地址存入a存储单元中。C语言规定*a代表a中存储的地址对应的存储单元中的数据(也就是解引用的意思),也就是访问*a就等于访问b,于是*a提供了通过a访问b中的数据的手段。

a表示a对应的存储单元中的数据。&a表示a对应的存储单元的地址。

当a声明的类型是int*时,a中存储的是一个存储单元的地址,而该存储单元中存储的数据是一个整数数值;通过*a可以访问(读取或修改)这个数值。a== &*a 都是该存储单元的地址。

当a声明的类型是int**时,a中存储的是一个存储单元的地址,而该存储单元中存储的数据是另外一个存储单元的地址,另外这个存储单元中存储的是一个整数数值;通过**a可以访问(读取或修改)这个数值。

其他说明

简单的来说,在C语言里地址叫指针。在C语言中的数组本质上其实也是指针,即:*a 等同于 a[]。

#include <stdio.h>
int main(){
    int *a;
    int b[2];
    int s;
    char *d="Hello world";
    b[0]=2;
    b[1]=9;
    a=b;
    for(s=0;s<2;s++)
        printf("a[i]=%d,b[i]=%d\n",a[s],b[s]);    
        printf("d[0]=%c,d[1]=%c\n",d[0],d[1]);
    return 0;
}

运行结果:
a[i]=2,b[i]=2
a[i]=9,b[i]=9
d[0]=H,d[1]=e


C++语言

C++中有一种C不存在的变量类型引用变量(简单说来为引用),尽管在C语言中用指针也可以实现类似的功能。引用,指针,地址是联系密切的概念。地址是在电脑内存中的地址(一般是一些变量的值在内存中的储存位置),指针是存地址的变量,所以指针可以“指向”内存地址。概念上讲,引用变量本质上是指针的另一个名字(但是并不能被编译器实例化)

C++中,&和*的用法基本一样。但是,C++中&的补充用法:引用。引用是C++引入的新语言特性,是C++常用的一个重要内容之一。引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

引用的声明方法:[类型标识符] &引用名=目标变量名;

#include <stdio.h>

int main(){
    
int a=3; 
int &ra=a;
printf("a=%d,&ra=%d",a,ra);
    
return 0;    
}

运行结果:
a=3,&ra=3


说明:

&在此不是求地址运算,而是起标识作用。类型标识符是指目标变量的类型。声明引用时,必须同时对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。

#include<iostream>
using namespace std;

void swap(int &p1, int &p2) //此处函数的形参p1, p2都是引用
{     
    int p; 
    p=p1; 
    p1=p2; 
    p2=p; 
}

int main(){
    int a,b;
    cin>>a>>b; //输入a,b两变量的值
    swap(a,b); //直接以变量a和b作为实参调用swap函数
    cout<<a<< ' ' <<b; //输出结果
return 0;
}

运行结果
输入:10 20
输出:20 10

传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
 

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐