C/C++问题,基础的,关于引号和地址,望高手解答(具体在下面):

请先看程序:
#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分!

首先
scanf("%d,%d,%d",&a,&b);

这句就是错的。你输入2个数,怎么能有三个%d呢?

其次
printf("\n加上引号:the max is %d\n","&z");
printf("\n去掉引号:the max is %d\n", &z);
这两句都是错的
正确写法是
printf("the max is %d\n", z);

scanf里需要&,但是printf里不需要&。
这里的&是取地址的意思。scanf的意义是从屏幕终端获取数据,然后存放如你给予的变量。所以你必须给予变量的地址,不然传入scanf函数的只是变量的拷贝,不可能对其有任何改变。
而printf是读取你输入的变量,打印到屏幕上。所以只需要你变量的拷贝就行。

scanf和printf都是变量个数不定的函数。但他们的参数还是有规律性的。即第一个参数必定是字符串,而且还是常字符串,即你一般用的用引号括起来的那个东西。之后的参数个数随便你输多少,输什么类型,程序都是能编译通过并运行的。但是想要得到真正有效的结果,而非不可预期的结果。那么第二个以后的参数要根据你第一个参数,即那串字符串来确定你类型和个数。具体就是scanf和printf函数会查找第一个字符串里的通配符,即%X,根据顺序,来一个个确定第2,3。。。个变量的类型。
比如"%d,%d,%d"
就代表除了一个个字符串,后面还有三个参数,都是int型的。
当然如果是scanf,那么就是int型的变量取地址,其实就是int型的指针。如果是printf,那么直接就是int型的追问

我有以下几点,望你理解:
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'; //这里会出错,系统不允许在那个存放这种常字符串的内存空间里进行写操作。

温馨提示:答案为网友推荐,仅供参考
第1个回答  2012-10-03
双引号中间的字符称为“字符串字面值”,就是说是一串字符串,就是&z。而没有引号的&z则是只变量z的地址(因为使用了取地址运算符&)。如果你要输出的是z的值,那么就把&去掉。

这个程序确实存在语法错误,编译器应该会报错。
#define STR "&z"是宏定义,就是说在预编译期间,将STR替换成"&z"。
//输入两个数,并输出其中的最大值及其内存地址
#include<cstdio>
#include<cstdlib>

int main()
{
//读取两个整数
std::printf("输入两个数:");
int a = 0, b = 0;
std::scanf("%d", &a);
std::scanf("%d", &b);

//获取两个整数中的最大值
int max = 0;
a > b ? max = a : max = b;

//输出最大值及其内存地址
std::printf("最大值为%d\n", max);
std::printf("最大值的地址为%d\n", &max);

//成功返回,结束程序
return EXIT_SUCCESS;
}

以上的写法是遵照C++的规范的。追问

你讲得很好!但我想知道的是,字符串&z的地址是什么呢?(注,这里的&z是字符串!)

追答

“&z”字符串被称为字面常量,在内存中会有一个专门的位置存储它,一般称为“文本区(text session)”,至于程序被载入内存后(就成为进程),就由操作系统来决定该放在哪里。

&z这个不是字符串了,它的含义是获取变量z的内存位置。内存在概念上是按编号进行排列的,就像一条街上的门牌号一样。z就如同在这条街上一座房子,需要有一个门牌号,&z就是要得到这个门牌号。

追问

我的问题是这样的,我来描诉下:
按照几位回答者所言,"&z"是显示堆中的地址,&z是z的地址(&z这个我懂)。于是,我第一个问题就是这个堆中的地址,如下:
1、如果说"&z"被放在堆中,那么printf()函数未免太变态,作为一个以根查找来录入的函数,他是怎么做到区分放在堆中还是栈中甚至是其他!故我不太相信,望解惑!
2、根据里讲,引号里的是指针,那么是否意味着,申请的指针,其空间也是开辟在堆中?

追答

我认为“&z”的位置肯定不会是在堆(heap)上,它应该是在栈(stack)上的。很多书籍将堆栈当作栈来讲述了,实际上这两个是有区别的。
所谓的堆、栈、静态存储区都是在内存的。区别是栈是存放与函数调用相关的一切东西,比如函数的局部变量、参数、返回值、寄存器值等的区域;静态存储区则是存放static修饰的变量,所以它们才具有很长的生命周期;堆则是与动态分配内存有关的,比如用malloc函数分配的内存就是在堆上,在堆上的变量最大特点就是不具名,即没有名字,所以才要使用指针来找到它。
双引号中的都应当是字符串。由于“&z”是printf函数中的一个参数,所以,它的位置应该是在栈上。

第2个回答  2012-10-03
printf("\n加上引号:the max is %d\n","&z");//"&z"用引号括起来是表示一个字符串,而%d要求一个整数,printf会把"&z"的地址输出。
printf("\n去掉引号:the max is %d\n", &z);//&z是取变量z的地址,而%d要求一个整数,printf会把变量z的地址输出。
那们"牛人"的
#define STR "&z"
main()
{
char *p = STR;
printf("%x",STR);
}
把STR用"&z"换一下。实质就是printf("%x","&z");和你上面第一种情况差不多,%x是用16进制把字符串"&z"的地址输出。
第3个回答  2012-10-03
他的意思是说,你加上引号,就变成了另一个量,实质是该字符串在堆中的地址,即所定义的宏STR,可以看成一个常量,你加上引号打印出的值就是该字符串存储位置的地址,不加引号是z变量所在内存中的地址。所以不同。追问

我终于明白了!
但我作为一个学生,我依然由几个问题,望赐教!
1、你是怎么知道"&z"的字符串&z,它的地址在堆中?
2、字符串&z在堆中的地址,指的是&的地址还是z的地址,或者别的什么地址?
3、我经常听别人说什么堆的地址,栈的地址?到底哪些可以归为堆的地址,那些归为栈的地址?
4、另外,根据里讲,引号里的是指针,那么是否意味着,申请的指针,其空间也是开辟在堆中?
望赐教!

追答

这么一问,我又翻了几本书,发现弄混了,C++字符串才放在堆上,C则不同。1.字符串常量的地址根据不同编译器会放在不同区域,一般是栈上,有些会放在文本区,但绝不会在堆上,堆是动态内存分配的区域(即malloc分配的)。2.如果是字符串“&z”,那么&也是一个字符,将用字符串首字母作为字符串地址,所以是“&”的地址。3.一般来说,malloc分配的空间在堆上,其他的一些数据放在栈上。4.同一,这个指针放到栈上了。我也是学生,慢慢学吧,等将来学学汇编+编译,这些底层的问题就清楚了。另外推荐你看看《c专家编程》,一般认为和《c陷阱与缺陷》配套的。

追问

可否加QQ?我的是1078577864!你的呢?

追答

我的是471671830

第4个回答  2012-10-03
有这样的想法啊? 加上引号那个是字符串 所在地址 用整型显示

而不加引号 就是z地址 用整形显示追问

你说加了引号就是显示字符串的地址,那么我想请问:
1、这里的字符串,是指&z么?
2、如果是,那么这里字符串的地址又指&的地址还是z的地址,或者别的什么地址?能具体么?我作为初学者,望赐教!

追答

&i 就是取i的地址

如果i是对象 或者变量 这就是变量 和对象的地址

如果 i 是指针

那么 &i 就是指针的指针

第5个回答  2012-10-03
都是错的
相似回答