请先看程序:
#include<stdio.h>
int main()
{
int a,b,z;
printf("输入两个数:");
scanf("%d,%d,%d",&a,&b);
if(a>b)
z=a;
else
z=b;
printf("\n加上引号:the max is %d\n","&z");
printf("\n去掉引号:the max is %d\n", &z);
return 0;
}
问:为什么加了引号和不加引号结果不一样?求赐教!
我所在的群(QQ群:256088680)里,
有个牛人说:“
#define STR "&z"
main()
{
char *p = STR;
printf("%x",STR);
}
这就是真相。”
可我压根没看懂,望高手赐教。
回答满意者额外加20~50分!
我有以下几点,望你理解:
1、我在百度上打字快了,不小心多打了个%d
2、你说的那些我懂,不但知道这些,我还知道printf的函数原型是printf(char *format,string......),你在后面讲的着一些都是根据其函数原型来的,我之前已经知道了!
我有一个问题
你说printf("\n加上引号:the max is %d\n","&z");printf("\n去掉引号:the max is %d\n", &z);
两句都有错误。
我不赞同,由函数原型推导。
望解释!
你的第二点就是错的
printf原型是printf(const char *string,......)。所以只有第一个参数是字符串,其他都是可变的。可变就是任何类型都能编译通过,不会弹出编译错误和链接错误。但实际上不能得到你想要的结果。
之前我也大致说过printf辨认参数的原理了。所以编译器并不会帮你检查出了第一个变量外的变量类型,数量是否正确。再者,因为除了第一个变量都是不定的,所以编译器没有为你将除了第一个变量外的变量进行自动类型转换的功能。打个比方:
int iT=10;
float fT=iT;
在这里编译器就自动对iT做了类型转换,自动转成浮点型后再赋值给fT。
再比如
void fun(float a);
int a=10;
fun(a);
这里编译器同样会给你做类型转换,将a
转换为浮点。因为编译器知道这里必须为浮点,同时int又确实能转换为浮点。
但是printf之后的参数类型都是不定的,所以编译器没有给你做类型转换的根据,所以就不做啦。
printf("\n加上引号:the max is %d\n","&z")
你这句,编译没问题。第一个参数正确,第二个参数是个const char*
因为这是不定的参数,函数压根认不出它是个字符串。函数内部只能把它当作一个char常指针的实际值来用,即这串字符串的地址来用。
然后用这个10进制的内存地址来代替%d,然后显示到屏幕上。
printf("\n加上引号:the max is %d\n",&z)
这里情况类似,只不过传入的不是字符串的地址,而是z这个变量的地址。
显然z这个变量的地址和"&z"这个字符串的首地址没可能一样的。
你那个所谓的高手,只是用%x的格式,即16进制格式输出这个地址。因为一般内存地址都是用16进制表示的。不过这对你的问题有什么帮助吗?不过故作高深罢了。
看你这个我觉得很好,于是又有几个问题:
1、printf既然不能识别第一个参数以外的变量类型,那么,我们输入%d/f等等,他为什么能对应显示?为什么加了&就能显示变量地址,加了" "再加&就能显示&z字符串的地址?他靠什么判别或说显示?
2、你说"函数..认不出...把它当作一个char常指针的实际值来用,即这串字符串的地址来用",这里常指针的实际值就是&z的地址?那么&z存在哪里?楼下说是堆的地址!你觉得呢?
1.函数的变量其实是放在一个栈里的。编译器根据一些指示符,要么把参数从左到右,要么从右到左,一个个压入栈中传给函数。函数再一个个按照函数声明里的参数类型,取出来。说栈你可能不懂,那来个类比。你可以认为函数的参数是塞在一个数组里的。函数根据函数声明里的参数类型,将其取出来。比如
void fun(int a, float b, short c);
那么参数存在这样一个类似数组A的东西里。因为sizeof(int)==4,sizeof(float)==4,sizeof(short)==2,
所以函数内部会先取A中前4个字节,以int类型的方式读取,存放到a里。然后再取接下来4字节,以float方式读取,存放到b,最后读取2字节,存放到c。因为fun的声明里所有参数类型都是明确无误的。所以编译时,编译器会负责检查你传入的参数是否符合。
对于printf(const char* string, ...)
所有参数仍旧塞在一个类似数组的A里。sizeof(const char*)==4(所有指针类型的大小都是一样的,一般为4字节),所以printf会先取A的前4字节,以const char*存放到string。但是对于A之后的东西,printf就不能自动辨别了。这时printf内部会根据刚刚得到的string的内容来辨别剩下的参数。
扫描string的内容,若遇到%XXX,就说明接下来会有个特点类型的参数存放在A中。
比如%d就是指int。因为32位相当于4字节,于是函数继续读取A中接下来4个字节,以int类型存到函数内部的局部变量里。
说到这里就能看出问题了。除了printf的第一个变量,之后的变量的辨识完全靠string的内容。所以如果你string里分明是%d,但你其实输入的是别的什么奇怪的类型。但是电脑可不管这些,仍旧照着string内容分配余下的参数。
比如你string里分明是%d,输入的确是double,sizeof(double)==8,所以printf会截取double类型前4字节数据,以int类型存到相应局部变量里。
上面那是类型的大小都不匹配的情况。但是很多时候类型的大小还是匹配的,就是类型本身不匹配。比如你问题中的%d对应int,4字节大,"&z"是const char*,因为是个指针,也是4字节大。指针在内存中的指其实就是个32位的内存地址址。printf直接当成int读取后,读到的自然就是这个地址值。这些其实都是巧合,没你想得那么智能。
2.“&z”是个字符串,相当于{‘&’,‘z’,‘\0’},‘\0’是字符串结束符。所以这东西和z完全木有关系。他的地址就是字符数组的首地址,即第一个字符‘&’在内存中的地址。
&z则是取z这个变量的地址。因为z是int,所以&z就是int*,即一个int型指针。
首先,对你表示感谢,你讲的很详细!
其次,我还有一个问题:你说const char*string,他的大小为4,可是我们输入的第一个参数通常是一串很长的字符,4个字节显然不够,所以我猜string应该是存入一个数组的首地址,而把printf中" "(引号)里的数据放进那串数组?可如果是这样,数组从何而来我又想不通了?于是,能否更进一步近一点,至于栈,我多少还是知道一点点!
第三,我想加你QQ,好么?
晕,你问题好多啊
引号引起来的字符串有两种方法声明:
char string[] = "test string";
这种方法获得的数组是栈上的,相当于
char string[] = {'t','e','s','t',' ','s','t','r','i','n','g','\0'};
即你用平时的方法建立一个数组,往里面填入char类型的字符。
另一种声明方法:
const char* string = "test string";
这里的字符串同样也是数组,但是不是位于栈上,而是位于一个专门存放这种引号括起来的字符串的内存空间。一般在这内存空间上申请的变量好像是全局的。下面举个例子。
const char* fun()
{
char string[] = "test string";
return string;
}
void main()
{
printf(fun());
system("pause");
}
这样是得不到test string的,因为string是栈上的变量,除了函数fun,就被系统自动销毁了。
但是
const char* fun()
{
const char* string = "test string";
return string;
}
void main()
{
printf(fun());
system("pause");
}
这样却能得到test string,因为这里的string是放在一个专门存放这种常字符串的内存空间里的,所以是全局的。
值得注意的是
const char* string = "test string";
虽然也能写成
char* string = "test string";
并编译通过。但不建议这样。因为"test string"本身是不可改变的,即
char* string = "test string";
string[3]='a'; //这里会出错,系统不允许在那个存放这种常字符串的内存空间里进行写操作。
你讲得很好!但我想知道的是,字符串&z的地址是什么呢?(注,这里的&z是字符串!)
追答“&z”字符串被称为字面常量,在内存中会有一个专门的位置存储它,一般称为“文本区(text session)”,至于程序被载入内存后(就成为进程),就由操作系统来决定该放在哪里。
&z这个不是字符串了,它的含义是获取变量z的内存位置。内存在概念上是按编号进行排列的,就像一条街上的门牌号一样。z就如同在这条街上一座房子,需要有一个门牌号,&z就是要得到这个门牌号。
我的问题是这样的,我来描诉下:
按照几位回答者所言,"&z"是显示堆中的地址,&z是z的地址(&z这个我懂)。于是,我第一个问题就是这个堆中的地址,如下:
1、如果说"&z"被放在堆中,那么printf()函数未免太变态,作为一个以根查找来录入的函数,他是怎么做到区分放在堆中还是栈中甚至是其他!故我不太相信,望解惑!
2、根据里讲,引号里的是指针,那么是否意味着,申请的指针,其空间也是开辟在堆中?
我认为“&z”的位置肯定不会是在堆(heap)上,它应该是在栈(stack)上的。很多书籍将堆栈当作栈来讲述了,实际上这两个是有区别的。
所谓的堆、栈、静态存储区都是在内存的。区别是栈是存放与函数调用相关的一切东西,比如函数的局部变量、参数、返回值、寄存器值等的区域;静态存储区则是存放static修饰的变量,所以它们才具有很长的生命周期;堆则是与动态分配内存有关的,比如用malloc函数分配的内存就是在堆上,在堆上的变量最大特点就是不具名,即没有名字,所以才要使用指针来找到它。
双引号中的都应当是字符串。由于“&z”是printf函数中的一个参数,所以,它的位置应该是在栈上。
我终于明白了!
但我作为一个学生,我依然由几个问题,望赐教!
1、你是怎么知道"&z"的字符串&z,它的地址在堆中?
2、字符串&z在堆中的地址,指的是&的地址还是z的地址,或者别的什么地址?
3、我经常听别人说什么堆的地址,栈的地址?到底哪些可以归为堆的地址,那些归为栈的地址?
4、另外,根据里讲,引号里的是指针,那么是否意味着,申请的指针,其空间也是开辟在堆中?
望赐教!
这么一问,我又翻了几本书,发现弄混了,C++字符串才放在堆上,C则不同。1.字符串常量的地址根据不同编译器会放在不同区域,一般是栈上,有些会放在文本区,但绝不会在堆上,堆是动态内存分配的区域(即malloc分配的)。2.如果是字符串“&z”,那么&也是一个字符,将用字符串首字母作为字符串地址,所以是“&”的地址。3.一般来说,malloc分配的空间在堆上,其他的一些数据放在栈上。4.同一,这个指针放到栈上了。我也是学生,慢慢学吧,等将来学学汇编+编译,这些底层的问题就清楚了。另外推荐你看看《c专家编程》,一般认为和《c陷阱与缺陷》配套的。
追问可否加QQ?我的是1078577864!你的呢?
追答我的是471671830
你说加了引号就是显示字符串的地址,那么我想请问:
1、这里的字符串,是指&z么?
2、如果是,那么这里字符串的地址又指&的地址还是z的地址,或者别的什么地址?能具体么?我作为初学者,望赐教!
&i 就是取i的地址
如果i是对象 或者变量 这就是变量 和对象的地址
如果 i 是指针
那么 &i 就是指针的指针