如何向初学者解释C指针(声明与一元运算符)?

我最近很高兴地解释了一个C编程初学者的指针,并偶然发现了以下困难。 如果你已经知道如何使用指针,可能看起来并不是一个问题,但是试着用清晰的头脑来看下面的例子:

int foo = 1; int *bar = &foo; printf("%p\n", (void *)&foo); printf("%i\n", *bar); 

对于绝对的初学者来说,输出可能是令人惊讶的。 在第二行他/她刚刚宣布*酒吧是&富,但在第4行,结果是*酒吧实际上是富而不是&富!

你可能会说,这个混淆源于*符号的模糊性:在第2行中,它被用来声明一个指针。 在第4行中,它被用作一个一元运算符,它获取指针指向的值。 两个不同的东西,对吧?

然而,这个“解释”根本没有帮助初学者。 它通过指出一个微妙的差异来引入一个新的概念。 这不可能是教它的正确方法。

那么Kernighan和Ritchie怎么解释呢?

一元运算符*是间接或解引用运算符; 当应用于指针时,它访问指针指向的对象。 […]

指针ip, int *ip的声明是用作助记符的; 它表示*ipexpression式是一个int。 variables声明的语法模仿variables可能出现的expression式的语法

int *ip应该像“ *ip将返回一个int ”读取? 但是,为什么宣布后的任务不遵循这种模式呢? 如果初学者想要初始化variables呢? int *ip = 1 (读取: *ip将返回一个int并且int1 )将无法按预期工作。 概念模型看起来并不一致。 我在这里错过了什么?


编辑: 它试图总结这里的答案

为了让学生在不同的语境中理解*符号的含义,他们必须首先明白上下文确实是不同的。 一旦他们明白上下文是不同的(即任务的左侧和普通expression之间的区别),理解差异是不是太过于认知上的飞跃。

首先说明一个variables的声明不能包含运算符(通过在variables声明中放入一个-+符号来简单地导致错误来certificate这一点)。 然后继续显示expression式(即在赋值的右侧)可以包含运算符。 确保学生理解expression式和variables声明是两个完全不同的上下文。

当他们理解上下文不同时,可以继续解释,当*符号位于variables标识符前面的variables声明中时,意味着“将该variables声明为指针”。 然后你可以解释一下,当在一个expression式(作为一元运算符)中使用时, *符号是“解引用运算符”,它意味着“在地址上的值”而不是它早先的含义。

为了真正说服你的学生,请解释一下,C的创build者可以使用任何符号来表示解除引用操作符(也就是说他们可以使用@来代替),但是无论出于何种原因,他们都会做出devise决定来使用*

总而言之,没有办法解释上下文是不同的。 如果学生不明白上下文是不同的,他们不明白为什么*符号可能意味着不同的事情。

速记之所以如此:

 int *bar = &foo; 

在你的例子中可能会令人困惑的是,它很容易误解为相当于:

 int *bar; *bar = &foo; // error: use of uninitialized pointer bar! 

当它实际上意味着:

 int *bar; bar = &foo; 

这样写出来,variables声明和赋值是分开的,不存在这种混淆的可能性,在K&R报价中描述的使用声明并行性是完美的:

  • 第一行声明了一个variablesbar ,例如*bar是一个int

  • 第二行将foo的地址赋给bar ,使*bar (一个int )成为foo (也是一个int )的别名。

当向初学者引入C指针语法时,一开始坚持这种将指针声明与赋值分离的风格可能是有帮助的,一旦指针使用的基本概念被引入,只引入组合的简写语法(对潜在的混淆进行适当的警告) C已经被充分内化了。

宣言短

很高兴知道声明和初始化之间的区别。 我们将variables声明为types并用值初始化它们。 如果我们同时做这两个,我们经常把它称为一个定义。

int a; a = 42; int a; a = 42;

 int a; a = 42; 

我们声明一个名为aint 。 然后我们通过给它一个值来初始化它。

2. int a = 42;

我们声明并且int命名为a,并给它赋值42.它被初始化为42 。 一个定义。

3. a = 43;

当我们使用variables我们说我们他们进行操作a = 43是一个赋值操作。 我们将数字43分配给variablesa。

 int *bar; 

我们声明bar是一个指向int的指针。 说

 int *bar = &foo; 

我们声明bar并用foo的地址初始化它。

在初始化bar之后,我们可以使用相同的操作符(星号)来访问和操作foo的值。 没有操作员,我们访问并操作指针指向的地址。

除此之外,我让照片说话。

什么

关于正在发生的事情的简化标准。 (如果你想暂停的话,这里是一个玩家版本 )

ASCIIMATION

第二条语句int *bar = &foo; 可以在记忆中形象地看待,

  bar foo +-----+ +-----+ |0x100| ---> | 1 | +-----+ +-----+ 0x200 0x100 

现在bar是包含地址& foo inttypes的指针。 使用一元运算符*我们尊重检索指针bar指向的值。

编辑 :我的做法理解指针初学者将开始解释一个variables的memory address

Memory Address:每个variables都有一个与OS提供的地址相关联的地址。 在int a;&a是variablesa地址。

继续解释C某些基本types的variables,

Types of variables:variables可以保存各个types的值,但不包含地址。

 int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers:如上面所说的variables,例如

  int a = 10; // a contains value 10 int b; b = &a; // ERROR 

有可能分配b = a而不是b = &a ,因为variablesb可以保存值而不是地址,因此我们需要指针

Pointer or Pointer variables :如果variables包含地址,则称为指针variables。 在声明中使用*来通知它是一个指针。

 • Pointer can hold address but not value • Pointer contains the address of an existing variable. • Pointer points to an existing variable 

查看这里的答案和评论,似乎有一个普遍的共识,即所讨论的语法可能会让初学者感到困惑。 他们大多数提出了这样的一些build议:

  • 在显示任何代码之前,请使用图表,草图或animation来说明指针如何工作。
  • 在展示语法时, 解释星号符号的两个不同angular色 。 许多教程缺less或逃避那部分。 随之而来的是混乱(“当你将一个初始化的指针声明分解成一个声明和一个后来的赋值时,你必须记得去掉*”– comp.lang.c FAQ ), 我希望find一个替代方法,但是我想这是要走的路。

你可以写int* bar而不是int *bar来突出区别。 这意味着您将不会遵循K&R“声明模仿使用”的方法,而是使用Stroustrup C ++方法 :

我们不声明*bar是一个整数。 我们声明bar是一个int* 。 如果我们想在同一行初始化一个新创build的variables,很明显我们正在处理的是bar而不是*barint* bar = &foo;

缺点:

  • 你必须警告你的学生关于多指针声明问题( int* foo, bar vs int *foo, *bar )。
  • 你必须准备好他们的世界受到伤害 。 许多程序员希望看到variables名称旁边的星号,他们将花费很大的篇幅来certificate他们的风格。 许多风格指南明确强调了这一标记(Linux内核编码风格,NASA C风格指南等)。

编辑:一个不同的方法已经被提出,是去K&R“模仿”的方式,但没有“速记”语法(见这里 )。 只要你忽略同一行中的一个声明和一个任务 ,一切都将看起来更加连贯。

但是,迟早学生将不得不作为函数参数来处理指针。 指针作为返回types。 并指向function。 你将不得不解释int *func();int (*func)(); 。 我想迟早会有事情崩溃。 或许更早比以后更好。

K&R风格有利于int *p和Stroustrup风格有利于int* p ; 两种语言在每种语言中都是有效的(而且意思是相同的),但正如Stroustrup所说的那样:

“int * p”之间的select 和“int * p” 不是关于是非,而是风格和重点。 C强调expression; 宣言往往被认为是一个必要的罪恶。 另一方面,C ++则强调types。

现在,既然你在这里教C,那么你就应该强调更多types的expression,但是有些人可以比另一个更快速地理解一个重点,而这关系到他们而不是语言。

因此, 有些人会觉得int*是一个不同于int东西,并从那里开始的想法更容易。

如果有人确实快速地思考使用int* bar作为一个不是int的东西,而是一个指向int的指针,那么他们很快就会看到*bar正在做一些事情bar ,其余的将随之而来。 一旦你完成了,你可以稍后解释为什么C编程者更喜欢int *bar

或不。 如果每个人都有一种理解这个概念的方法,那么首先就不会有任何问题,而向一个人解释这个概念的最佳方式并不一定是向另一个人解释这个概念的最好方式。

TL;博士:

问:如何向初学者解释C指针(声明与一元运算符)?

A:不。 解释初学者的指针,并向他们展示如何用C语法表示他们的指针概念。


我最近很高兴地解释了一个C编程初学者的指针,并偶然发现了以下困难。

海事组织的C语法并不可怕,但也不是很好:如果你已经理解了指针,那么它也不是一个很大的障碍,在学习它们方面也没有任何帮助。

因此:从解释指针开始,确保他们真正理解它们:

  • 用方框和箭头图解释它们。 你可以不用hex地址,如果它们不相关,只显示箭头指向另一个盒子,或者指向一些nul符号。

  • 用伪代码解释:只写foo的地址存储在bar的值

  • 那么,当你的新手明白什么是指针,为什么,以及如何使用它们; 然后显示到C语法的映射。

我怀疑K&R文本没有提供概念模型的原因是他们已经理解了指针 ,并且当时也可能认为其他所有的程序员都这么做了。 助记符只是提醒从良好理解的概念到语法的映射。

这个问题在开始学习C的时候有些困惑

以下是可帮助您开始的基本原则:

  1. C中只有几个基本types:

    • char :大小为1字节的整数值。

    • short :大小为2个字节的整数值。

    • long :长度为4个字节的整数值。

    • long long :长度为8个字节的整数值。

    • float :一个大小为4字节的非整数值。

    • double :一个大小为8字节的非整数值。

    请注意,每种types的大小通常由编译器定义,而不是由标准定义。

    整数typesshortlonglong long通常跟着int

    然而,这不是必须的,你可以在没有int情况下使用它们。

    或者,你可以只声明int ,但不同的编译器可能会有不同的解释。

    所以总结一下:

    • shortshort int相同,但不一定与int相同。

    • longlong int相同,但不一定与int相同。

    • long longlong long int相同,但不一定与int相同。

    • 在给定的编译器中, intshort intlong intlong long int

  2. 如果你声明了某个types的variables,那么你也可以声明另一个指向它的variables。

    例如:

    int a;

    int* b = &a;

    所以实质上,对于每个基本types,我们也有相应的指针types。

    例如: shortshort*

    有两种方法来“观察”variablesb (这可能会让大多数初学者感到困惑)

    • 你可以把b看作是一个int*types的variables。

    • 您可以将*b视为inttypes的variables。

    因此,有些人会声明int* b ,而其他人会声明int *b

    但事实是,这两个声明是相同的(空间是没有意义的)。

    您可以使用b作为指向整数值的指针,或使用*b作为实际指向的整数值。

    您可以获取(读取)指向的值: int c = *b

    你可以设置(写)指定的值: *b = 5

  3. 一个指针可以指向任何内存地址,而不是指向之前声明的某个variables的地址。 但是,在使用指针来获取或设置位于指向内存地址的值时,您必须小心。

    例如:

    int* a = (int*)0x8000000;

    在这里,我们有a指向内存地址0x8000000的variables。

    如果这个内存地址没有被映射到程序的内存空间中,那么任何使用*a读或写操作都将导致程序崩溃,这是由于内存访问冲突造成的。

    您可以安全地更改a的值,但是您应该非常小心地更改*a的值。

  4. typesvoid*是例外的,它没有可用的相应“值types”(即,你不能声明void a )。 此types仅用作指向内存地址的通用指针,而不指定驻留在该地址中的数据的types。

也许更多地通过它更容易:

 #include <stdio.h> int main() { int foo = 1; int *bar = &foo; printf("%i\n", foo); printf("%p\n", &foo); printf("%p\n", (void *)&foo); printf("%p\n", &bar); printf("%p\n", bar); printf("%i\n", *bar); return 0; } 

让他们告诉你他们期望输出在每一行上,然后让他们运行程序,看看发生了什么。 解释他们的问题(在那里的裸体版本肯定会提示几个 – 但你可以担心后来的风格,严格性和可移植性)。 然后,在他们的头脑变得过度糊涂或变成午饭僵尸之前,编写一个带有价值的函数,以及一个带有指针的函数。

以我的经验来看,“为什么这样打印?” 然后立即向大家展示为什么这个function可以通过动手操作(作为stringparsing/数组处理等一些基本的K&R材料的前奏)而在函数参数中有用,这使得课程不仅有意义而且坚持。

下一步是让他们向解释i[0]关系。 如果他们能做到这一点,他们不会忘记,你可以开始谈论结构,即使提前一点,就会沉浸其中。

上面关于盒子和箭头的build议也是不错的,但是它也可能让人对关于记忆是如何工作的全面讨论感到兴奋 – 这是一个必须在某个时刻发生的讨论,但是可能会马上分散注意力:如何解释C中的指针表示法

expression式 *bar的types是int ; 因此, variables (和expression式) barint * 。 由于variables具有指针types,因此其初始值设定项也必须具有指针types。

指针variables的初始化和赋值有一个不一致的地方, 这只是一个必须以艰苦的方式学习的东西。

我宁愿读它作为第一*应用于intbar

 int foo = 1; // foo is an integer (int) with the value 1 int* bar = &foo; // bar is a pointer on an integer (int*). it points on foo. // bar value is foo address // *bar value is foo value = 1 printf("%p\n", &foo); // print the address of foo printf("%p\n", bar); // print the address of foo printf("%i\n", foo); // print foo value printf("%i\n", *bar); // print foo value 
 int *bar = &foo; 

Question 1 :什么是bar

Ans :这是一个指针variables(types为int )。 一个指针应指向一些有效的内存位置,稍后应使用一元运算符*取消引用(* bar),以读取存储在该位置的值。

Question 2 :什么是&foo

Ans :foo是一个inttypes的variables,它存储在一个有效的内存位置,我们从运算符中得到的位置&所以现在我们有一些有效的内存位置&foo

所以这两个放在一起,即指针需要的是一个有效的内存位置,这是由&foo得到的&foo所以初始化是好的。

现在指针bar指向有效的内存位置,存储在其中的值可以被解引用,即*bar

你应该指出一个初学者*在声明和expression中有不同的含义。 如你所知,expression式中的*是一个一元运算符,*在声明中不是一个运算符,只是一种与types相结合的语法,让编译器知道它是一个指针types。 最好说一个初学者,“*有不同的含义。为了理解*的含义,你应该find哪里使用*

我认为魔鬼在这个空间里。

我会写(不仅为初学者,而且为我自己):int * bar =&foo; 而不是int * bar =&foo;

这应该certificate语法和语义之间的关系是什么

已经注意到*有多个angular色。

还有一个简单的想法可能会帮助初学者掌握一些东西:

认为“=”也有多个angular色。

当赋值与声明在同一行上使用时,可以将其视为构造函数调用,而不是任意赋值。

当你看到:

 int *bar = &foo; 

认为这几乎等同于:

 int *bar(&foo); 

圆括号优先于星号,因此“&foo”更容易直观地归因于“bar”而不是“* bar”。

如果问题是语法,则可以使用template / using显示等效的代码。

 template<typename T> using ptr = T*; 

这可以作为

 ptr<int> bar = &foo; 

之后,比较正常/ C语法与此C ++唯一的方法。 这对于解释const指针也很有用。

由于*符号在C中可以有不同的含义,这取决于它被使用的事实,所以引起了混淆的根源。 为了解释初学者的指针,应该解释不同上下文中*符号的含义。

在声明中

 int *bar = &foo; 

*符号不是间接运算符 。 相反,它有助于指定bar的types,通知编译器bar指向int指针 。 另一方面,当它出现在声明中时, *符号(当用作一元运算符时 )执行间接。 因此,声明

 *bar = &foo; 

会是错误的,因为它将foo的地址分配给bar指向的对象,而不是bar自身。

“也许把它写成int *吧更明显的是,星星实际上是types的一部分,而不是标识符的一部分。” 所以我呢。 我说,这有点像types,但只有一个指针名称。

“当然,这会让你陷入像int * a,b这类不直观的东西的不同问题。

前几天我看到这个问题,后来碰巧在Go Blog上看到Go的types声明的解释。 它首先给出一个Ctypes声明的帐户,这似乎是一个有用的资源添加到这个线程,即使我认为已经有更完整的答案。

C采用了一种不寻常的和巧妙的方法来声明语法。 我们不用特殊的语法来描述types,而是写一个涉及被声明的项目的expression式,并且说明这个expression式将具有什么types。 从而

 int x; 

声明x是一个int:expression式'x'将有inttypes。 一般来说,为了弄清楚如何编写新variables的types,编写一个涉及该variables的expression式,该expression式计算为基本types,然后将基本types放在左边,expression式放在右边。

因此,声明

 int *p; int a[3]; 

状态p是一个指向int的指针,因为'* p'的types是int,而a是一个整数的数组,因为a [3](忽略特定的索引值,它被打成数组大小) INT。

(它继续描述如何将这种理解扩展到函数指针等)

这是我以前没有想过的一种方式,但它似乎是一个非常简单的方法来解释语法的重载。

在这里,你必须使用,理解和解释编译器的逻辑,而不是人的逻辑(我知道, 是一个人,但在这里你必须模仿电脑…)。

当你写

 int *bar = &foo; 

编译器组成那样

 { int * } bar = &foo; 

That is : here is a new variable, its name is bar , its type is pointer to int, and its initial value is &foo .

And you must add : the = above denotes an initialization not an affectation, whereas in following expressions *bar = 2; it is an affectation

Edit per comment:

Beware : in case of multiple declaration the * is only related to the following variable :

 int *bar = &foo, b = 2; 

bar is a pointer to int initialized by the address of foo, b is an int initialized to 2, and in

 int *bar=&foo, **p = &bar; 

bar in still pointer to int, and p is a pointer to a pointer to an int initialized to the address or bar.

I would explain that ints are objects, as are floats etc. A pointer is a type of object whose value represents an address in memory ( hence why a pointer defaults to NULL ).

When you first declare a pointer you use the type-pointer-name syntax. It's read as an "integer-pointer called name that can point to the address of any integer object". We only use this syntax during decleration, similar to how we declare an int as 'int num1' but we only use 'num1' when we want to use that variable, not 'int num1'.

int x = 5; // an integer object with a value of 5

int * ptr; // an integer with a value of NULL by default

To make a pointer point to an address of an object we use the '&' symbol which can be read as "the address of".

ptr = &x; // now value is the address of 'x'

As the pointer is only the address of the object, to get the actual value held at that address we must use the '*' symbol which when used before a pointer means "the value at the address pointed to by".

std::cout << *ptr; // print out the value at the address

You can explain briefly that ' ' is an 'operator' that returns different results with different types of objects. When used with a pointer, the ' ' operator doesn't mean "multiplied by" anymore.

It helps to draw a diagram showing how a variable has a name and a value and a pointer has an address (the name) and a value and show that the value of the pointer will be the address of the int.

Basically Pointer is not a array indication. Beginner easily thinks that pointer looks like array. most of string examples using the

"char *pstr" it's similar looks like

"char str[80]"

But, Important things , Pointer is treated as just integer in the lower level of compiler.

Let's look examples::

 #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv, char **env) { char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address char *pstr0 = str; // or this will be using with // or char *pstr1 = &str[0]; unsigned int straddr = (unsigned int)pstr0; printf("Pointer examples: pstr0 = %08x\n", pstr0); printf("Pointer examples: &str[0] = %08x\n", &str[0]); printf("Pointer examples: str = %08x\n", str); printf("Pointer examples: straddr = %08x\n", straddr); printf("Pointer examples: str[0] = %c\n", str[0]); return 0; } 

Results will like this 0x2a6b7ed0 is address of str[]

 ~/work/test_c_code$ ./testptr Pointer examples: pstr0 = 2a6b7ed0 Pointer examples: &str[0] = 2a6b7ed0 Pointer examples: str = 2a6b7ed0 Pointer examples: straddr = 2a6b7ed0 Pointer examples: str[0] = T 

So, Basically, Keep in mind Pointer is some kind of Integer. presenting the Address.

A pointer is just a variable used to store addresses.

Memory in a computer is made up of bytes (A byte consists of 8 bits) arranged in a sequential manner. Each byte has a number associated with it just like index or subscript in an array, which is called the address of the byte. The address of byte starts from 0 to one less than size of memory. For example, say in a 64MB of RAM, there are 64 * 2^20 = 67108864 bytes . Therefore the address of these bytes will start from 0 to 67108863 .

在这里输入图像说明

Let's see what happens when you declare a variable.

int marks;

As we know an int occupies 4 bytes of data (assuming we are using a 32-bit compiler) , so compiler reserves 4 consecutive bytes from memory to store an integer value. The address of the first byte of the 4 allocated bytes is known as the address of the variable marks . Let's say that address of 4 consecutive bytes are 5004 , 5005 , 5006 and 5007 then the address of the variable marks will be 5004 . 在这里输入图像说明

Declaring pointer variables

As already said a pointer is a variable that stores a memory address. Just like any other variables you need to first declare a pointer variable before you can use it. Here is how you can declare a pointer variable.

Syntax: data_type *pointer_name;

data_type is the type of the pointer (also known as the base type of the pointer). pointer_name is the name of the variable, which can be any valid C identifier.

Let's take some examples:

 int *ip; float *fp; 

int *ip means that ip is a pointer variable capable of pointing to variables of type int . In other words, a pointer variable ip can store the address of variables of type int only . Similarly, the pointer variable fp can only store the address of a variable of type float . The type of variable (also known as base type) ip is a pointer to int and type of fp is a pointer to float . A pointer variable of type pointer to int can be symbolically represented as ( int * ) . Similarly, a pointer variable of type pointer to float can be represented as ( float * )

After declaring a pointer variable the next step is to assign some valid memory address to it. You should never use a pointer variable without assigning some valid memory address to it, because just after declaration it contains garbage value and it may be pointing to anywhere in the memory. The use of an unassigned pointer may give an unpredictable result. It may even cause the program to crash.

 int *ip, i = 10; float *fp, f = 12.2; ip = &i; fp = &f; 

Source: thecguru is by far the simplest yet detailed explanation I have ever found.