我在学习C上遇到了一些困难,我真的没有别的地方可以寻求建议了。我来自OOP语言的列表,比如JavaScript和主要的Python,所以C是一个很大的变化,为了学习基本面,我遇到了很多困难。我最初是从Zed的“艰难地学习C”开始的,但他并没有真正在书中教任何东西。是的,他让你写了很多代码,并改变了一些东西,但我并不是在学习为什么代码能工作,这只会导致更多的混乱,因为这些例子都是复杂的。
的主要问题是变量和指针之间的区别(我认为这是非常不同的,直到我在下面看到一些示例,这完全模糊了两者之间的界限)。
例如,,我理解声明和初始化一个名为a的int和一个指针,p看起来如下所示:
int a;
int *p;
a = 12;
p = &a;让我困惑的是,当您声明看起来像指针的变量时,但是实际上根本不是指针(或者它们是?)。例如:
char *string = "This is a string";
printf("%s\n", string);当定义和初始化string时,它是什么?是指针吗?如果是的话,为什么在printf函数中打印它时不取消引用呢?有很多这样的例子让我感到困惑。
我遇到的另一个毫无意义的例子:
int i;
scanf("%d", &i);这个函数是如何更新整数i的值的,当符号应该引用变量在内存中的位置而不是值的时候?数组和结构变得更加复杂,这就是我停止的地方,并决定我需要找到一些建议。
我真的觉得发布这样一个问题很尴尬,但每个人都是从某个地方开始的。这些基本原理我知道我需要能够理解才能继续前进,但当我看到与我刚刚学到的东西相矛盾的代码示例时,我很难理解它。我知道这是一个非常普遍的问题,但我希望你们中的一些人可以解释这些基本知识,或者为我指明一个更好地学习/理解这一点的方向。我所见过的大多数视频教程都过于笼统,与在线的文本教程一样,它们会告诉你如何做一些事情,但不要解释,这会导致一些严重的问题。
发布于 2013-12-29 09:15:03
我将尝试与其他人稍微不同地解释这一点。
考虑到您来自JavaScript和Python,在讨论指针时,我将避免使用“引用”一词。这是因为他们虽然相似,但却不一样。
什么是指针?
指针是存储地址的变量。就这么简单。指向int的指针存储存储int的地址。
当取消引用指针时,您告诉编译器操作,不是在地址上,而是在存储在该地址上的内容上。
int *p;
int a = 7;
p = &a;
*p = 5;*p = 5告诉编译器进入存储在p中的地址,并将值5存储在那里(因为指针p指向一个整数)。因此,只要我们可以使用变量,我们就可以使用取消引用的指针来代替变量。
你是否应该使用:
int *p;
p = 5;然后将5的地址(一个内存位置)分配给指针(在内存中的任何位置)。如果您尝试使用不允许的内存位置,您的程序可能会崩溃。
地址,地址
&运算符接受某个变量的地址。它真的不在乎它是什么,它甚至可以接受指针的地址。
int a;
int *p;
int **pp;
pp = &p;
p = &a;这就是它所能做的。
您的示例
字符串示例
char *string = "This is a string";
printf("%s\n", string);您之所以感到困惑,是因为在JavaScript和Python中,您可以将字符串视为可以容纳到变量中。它不可能!在C中,字符串是顺序存储在内存中的字符序列(一个字符数组)。这就是为什么我们可以使用指针。我们所做的就是让指针指向第一个字符,然后我们知道整个字符串在哪里(因为它是顺序的)。而且,在C中,我们不存储字符串的大小。相反,字符串从指针处开始,当我们遇到零字节'\0'或简单的0时结束。
扫描实例
int i;
scanf("%d", &i);scanf之所以获得i的地址,是因为它会将通过控制台输入的整数放入i所在的位置。这样,scanf实际上可以有多个返回的值,这意味着:
int i, j;
scanf("%d%d", &i, &j);是有可能的。因为scanf知道变量的地址,所以它可以用正确的值更新它们。
发布于 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风格的字符串操作函数(如strcpy、strlen等)也使用相同的过程.
这样做的一个原因是字符串往往有不同的长度,因此需要不同的内存,但是使用可变大小的数据类型是不方便的。因此,我们有一个关于内存中字符串格式的约定(本质上是\0终止符约定),我们只需指向带有char*的字符串。
如果取消引用一个char *,您将得到一个字符,而不是您可能预期的字符串,因此要小心。
为什么
scanf("%d", &i)被传递给int,而不是int本身?
原因是,在C中,函数参数是通过值传递的,这意味着当您将某个函数传递给一个函数时,它的一个副本被创建并传递给该函数,而不管函数对该副本做了什么,原始函数保持原样。这意味着什么?
首先,如果你有这样的功能:
void add_two(int i) {
i = i + 2;
}如果调用:int i = 3; add_two(i);,则i的值不会更改,因为add_two刚刚得到了i的副本。另一方面,如果你有这样的功能:
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编程语言。”这将比在线阅读教程好得多,而且从长远来看也是值得的。
发布于 2013-12-29 09:51:57
指针是一个变量,它保存内存地址,而不是值。
当你写
int a;您正在指定a可以容纳整数。
+-----+
| int |
+-----+a在内存中,a的地址被写成&a
+-----+
&a -> | int |
+-----+当你写
int *p;您正在指定p可以保存指向整数(即内存地址)的指针。
+---------+
p -> | int |
+---------+例如,它可以指向a
p = &a;int i;
scanf( "%d", &i );为了理解上面的内容,您需要了解函数参数是如何传递给函数的,以及函数实际上是什么。
函数也是一个指针,所以当您调用一个函数时,您只是告诉编译器您希望在某个内存地址上执行代码。堆栈用于在函数和调用者之间传递参数和结果。
所以
int foo(int n) { return 11; }
int j = 10;
int i = foo(j);告诉编译器它应该在address foo上执行程序集指令,但是首先在堆栈上推10 (j),然后在堆栈上推送j的副本并执行foo,当foo即将返回时,它在堆栈上推送11并返回,然后调用方弹出返回值11并分配给i。注意,它是值11的副本,被推送到堆栈上。
如果希望函数更改参数的值,则不能在堆栈上推送变量的副本。相反,您需要传递变量内存地址的副本。
int foo(int *p) { *p = 12; return 11; }
int j = 10;
int i = foo(&j);现在j是12,i是11。
scanf比我的示例要复杂一些,它可以使用可变数量的参数,并使用第一个参数("%d")作为格式说明符来确定以下参数的数据类型和大小。%d告诉它需要一个int指针。
https://stackoverflow.com/questions/20823542
复制相似问题