首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C基本面-变量和指针有问题

C基本面-变量和指针有问题
EN

Stack Overflow用户
提问于 2013-12-29 08:49:30
回答 6查看 303关注 0票数 3

我在学习C上遇到了一些困难,我真的没有别的地方可以寻求建议了。我来自OOP语言的列表,比如JavaScript和主要的Python,所以C是一个很大的变化,为了学习基本面,我遇到了很多困难。我最初是从Zed的“艰难地学习C”开始的,但他并没有真正在书中教任何东西。是的,他让你写了很多代码,并改变了一些东西,但我并不是在学习为什么代码能工作,这只会导致更多的混乱,因为这些例子都是复杂的。

的主要问题是变量和指针之间的区别(我认为这是非常不同的,直到我在下面看到一些示例,这完全模糊了两者之间的界限)。

例如,,我理解声明和初始化一个名为aint和一个指针,p看起来如下所示:

代码语言:javascript
运行
复制
int a;
int *p;

a = 12;
p = &a;

让我困惑的是,当您声明看起来像指针的变量时,但是实际上根本不是指针(或者它们是?)。例如:

代码语言:javascript
运行
复制
char *string = "This is a string";
printf("%s\n", string);

当定义和初始化string时,它是什么?是指针吗?如果是的话,为什么在printf函数中打印它时不取消引用呢?有很多这样的例子让我感到困惑。

我遇到的另一个毫无意义的例子:

代码语言:javascript
运行
复制
int i;
scanf("%d", &i);

这个函数是如何更新整数i的值的,当符号应该引用变量在内存中的位置而不是值的时候?数组和结构变得更加复杂,这就是我停止的地方,并决定我需要找到一些建议。

我真的觉得发布这样一个问题很尴尬,但每个人都是从某个地方开始的。这些基本原理我知道我需要能够理解才能继续前进,但当我看到与我刚刚学到的东西相矛盾的代码示例时,我很难理解它。我知道这是一个非常普遍的问题,但我希望你们中的一些人可以解释这些基本知识,或者为我指明一个更好地学习/理解这一点的方向。我所见过的大多数视频教程都过于笼统,与在线的文本教程一样,它们会告诉你如何做一些事情,但不要解释,这会导致一些严重的问题。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2013-12-29 09:15:03

我将尝试与其他人稍微不同地解释这一点。

考虑到您来自JavaScript和Python,在讨论指针时,我将避免使用“引用”一词。这是因为他们虽然相似,但却不一样。

什么是指针?

指针是存储地址的变量。就这么简单。指向int的指针存储存储int的地址。

去引用

当取消引用指针时,您告诉编译器操作,不是在地址上,而是在存储在该地址上的内容上。

代码语言:javascript
运行
复制
int *p;
int a = 7;

p = &a;
*p = 5;

*p = 5告诉编译器进入存储在p中的地址,并将值5存储在那里(因为指针p指向一个整数)。因此,只要我们可以使用变量,我们就可以使用取消引用的指针来代替变量。

你是否应该使用:

代码语言:javascript
运行
复制
int *p;

p = 5;

然后将5的地址(一个内存位置)分配给指针(在内存中的任何位置)。如果您尝试使用不允许的内存位置,您的程序可能会崩溃。

地址,地址

&运算符接受某个变量的地址。它真的不在乎它是什么,它甚至可以接受指针的地址。

代码语言:javascript
运行
复制
int a;
int *p;
int **pp;

pp = &p;
p = &a;

这就是它所能做的。

您的示例

字符串示例

代码语言:javascript
运行
复制
char *string = "This is a string";
printf("%s\n", string);

您之所以感到困惑,是因为在JavaScript和Python中,您可以将字符串视为可以容纳到变量中。它不可能!在C中,字符串是顺序存储在内存中的字符序列(一个字符数组)。这就是为什么我们可以使用指针。我们所做的就是让指针指向第一个字符,然后我们知道整个字符串在哪里(因为它是顺序的)。而且,在C中,我们不存储字符串的大小。相反,字符串从指针处开始,当我们遇到零字节'\0'或简单的0时结束。

扫描实例

代码语言:javascript
运行
复制
int i;
scanf("%d", &i);

scanf之所以获得i的地址,是因为它会将通过控制台输入的整数放入i所在的位置。这样,scanf实际上可以有多个返回的值,这意味着:

代码语言:javascript
运行
复制
int i, j;
scanf("%d%d", &i, &j);

是有可能的。因为scanf知道变量的地址,所以它可以用正确的值更新它们。

票数 6
EN

Stack Overflow用户

发布于 2013-12-29 09:14:52

我将回答您的具体问题:为什么printf不被传递一个取消引用的指针,为什么scanf被传递一个地址,希望这将使一些事情变得更清楚。

首先,按照惯例,C风格的字符串是内存中的一个字符数组,以一个\0字符(称为NUL终止符)结束。C样式字符串保持跟踪的方式是保持指向字符串的第一个字符的指针。

当调用printf("%s\n", some_str)时,some_str的类型是char*printf所做的是打印some_str指向的字符,然后打印后面的字符(在内存中,只需增加指针some_str),然后再打印字符,直到找到\0,然后停止打印。

其他C风格的字符串操作函数(如strcpystrlen等)也使用相同的过程.

这样做的一个原因是字符串往往有不同的长度,因此需要不同的内存,但是使用可变大小的数据类型是不方便的。因此,我们有一个关于内存中字符串格式的约定(本质上是\0终止符约定),我们只需指向带有char*的字符串。

如果取消引用一个char *,您将得到一个字符,而不是您可能预期的字符串,因此要小心。

为什么scanf("%d", &i)被传递给int,而不是int本身?

原因是,在C中,函数参数是通过值传递的,这意味着当您将某个函数传递给一个函数时,它的一个副本被创建并传递给该函数,而不管函数对该副本做了什么,原始函数保持原样。这意味着什么?

首先,如果你有这样的功能:

代码语言:javascript
运行
复制
void add_two(int i) {
  i = i + 2;
}

如果调用:int i = 3; add_two(i);,则i的值不会更改,因为add_two刚刚得到了i的副本。另一方面,如果你有这样的功能:

代码语言:javascript
运行
复制
void really_add_two(int *i) {
  *i = *i + 2;
}

现在,int i = 3; really_add_two(&i);将导致i具有5值。这是因为指向i的指针给出了really_add_two,并且函数修改了内存位置上的数据(通过取消引用)。

需要给scanf地址的原因与上面的相同。

如果您是认真学习C,您应该学习Kernighan和里奇,“C编程语言。”这将比在线阅读教程好得多,而且从长远来看也是值得的。

票数 3
EN

Stack Overflow用户

发布于 2013-12-29 09:51:57

指针是一个变量,它保存内存地址,而不是值。

当你写

代码语言:javascript
运行
复制
int a;

您正在指定a可以容纳整数。

代码语言:javascript
运行
复制
+-----+
| int | 
+-----+

a在内存中,a的地址被写成&a

代码语言:javascript
运行
复制
      +-----+
&a -> | int | 
      +-----+

当你写

代码语言:javascript
运行
复制
int *p;

您正在指定p可以保存指向整数(即内存地址)的指针。

代码语言:javascript
运行
复制
     +---------+
p -> |   int   |
     +---------+

例如,它可以指向a

代码语言:javascript
运行
复制
p = &a;
代码语言:javascript
运行
复制
int i;
scanf( "%d", &i );

为了理解上面的内容,您需要了解函数参数是如何传递给函数的,以及函数实际上是什么。

函数也是一个指针,所以当您调用一个函数时,您只是告诉编译器您希望在某个内存地址上执行代码。堆栈用于在函数和调用者之间传递参数和结果。

所以

代码语言:javascript
运行
复制
int foo(int n) { return 11; }

int j = 10;
int i = foo(j);

告诉编译器它应该在address foo上执行程序集指令,但是首先在堆栈上推10 (j),然后在堆栈上推送j的副本并执行foo,当foo即将返回时,它在堆栈上推送11并返回,然后调用方弹出返回值11并分配给i。注意,它是值11的副本,被推送到堆栈上。

如果希望函数更改参数的值,则不能在堆栈上推送变量的副本。相反,您需要传递变量内存地址的副本。

代码语言:javascript
运行
复制
int foo(int *p) { *p = 12; return 11; }

int j = 10;
int i = foo(&j);

现在j是12,i是11。

scanf比我的示例要复杂一些,它可以使用可变数量的参数,并使用第一个参数("%d")作为格式说明符来确定以下参数的数据类型和大小。%d告诉它需要一个int指针。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20823542

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档