**在C语言中是做啥的? [复制]
Posted
技术标签:
【中文标题】**在C语言中是做啥的? [复制]【英文标题】:What does ** do in C language? [duplicate]**在C语言中是做什么的? [复制] 【发布时间】:2016-05-03 11:16:05 【问题描述】:我是 C 新手,具有良好的 java 背景,我正在尝试理解指针和数组。
我知道下标operator[]
是数组定义的一部分,所以:
int numbers[] = 1,3,4,5;
将创建一个整数数组,在内存中表示为 16 个字节,4 个 4 个字节:
numbers[0] = 1, address 0061FF1C
numbers[1] = 3, address 0061FF20
numbers[2] = 4, address 0061FF24
numbers[3] = 5, address 0061FF28
但是,当涉及到指针时,我的知识开始崩溃,所以如果我要创建指向数组编号的指针,我会执行以下操作:
int *pNumbers = &numbers[0];
看起来像这样:
我猜它的大小是 4 个字节?
但是 **
我读为“指向指针的指针”,这对我来说毫无意义,为什么有人想要指向指针的指针,当然如果 a->b->c 那么 a->c 就足够了?我知道我遗漏了一些东西,它一定与数组有关,因为argv
可以是char[ ]
或char **
类型,如下所示:
int main(int argc, char **argv)
所以:
这是什么 (**
)?
它有什么用?
它在内存中是如何表示的?
【问题讨论】:
您想要一个指向指针的指针,原因与您想要指向任何东西的指针相同。例如,修改它指向的指针。 指向指针的指针非常有意义,并且一直在使用。此外,int
并不总是 4 个字节。在某些机器上是这样。在 64 位机器上,它可能是 8 个字节。在某些微处理器上,它是 2 个字节。 int
的大小是 sizeof (int)
。这将永远是正确的。
@Marged 我问了它有什么用途,另外两个问题我暗示我可能已经知道了,但我不是 100% 确定,这就是我明确提出的原因。我选择的答案很好地说明了如何使用这些指针,因为您标记的问题的答案说明了 * 和 & 运算符的工作原理,正如我根据我在问题中提出的内容已经知道的那样,请参阅“int *pNumbers = &numbers [0];"
@BenVoigt 我认为这个问题的答案帮助我弄清楚我的问题更多是内存模型而不是指针的指针。在 Java 中,我只是有一条规则,您可以操作对象,但不能操作原语。到目前为止,这似乎对我有用,我已经编程了将近两年,但现在我开始质疑我对内存模型的了解。除非您可以说 C 只有原语,并且指针允许您通过传递引用原始原语的值来扩展原语的范围?或者我只是完全愚蠢。 (这完全有可能)
@immibis 这是我对“按价值传递”和按引用传递的理解“我现在明白了:)。谢谢!
【参考方案1】:
我想我会在这里添加我自己的答案,因为每个人都做得很棒,但我真的很困惑指针指向指针的意义。我之所以想出这个是因为我的印象是除了指针之外的所有值都是按值传递的,而指针是通过引用传递的。请参阅以下内容:
void f(int *x)
printf("x: %d\n", *x);
(*x)++;
void main()
int x = 5;
int *px = &x;
f(px);
printf("x: %d",x);
会产生:
x: 5
x: 6
这使我认为(出于某种原因)指针是通过引用传递的,因为我们正在传递指针,对其进行操作,然后分解并打印新值。如果您可以在函数中操作指针...为什么要有指向指针的指针才能操作指针开头!
这对我来说似乎是错误的,这是正确的,因为当您已经可以在函数中操作指针时,使用指针来操作指针会很愚蠢。不过,C 的东西;是一切都按值传递,甚至是指针。让我用一些伪值而不是地址来进一步解释。
//this generates a new pointer to point to the address so lets give the
//new pointer the address 0061FF28, which has the value 0061FF1C.
void f(int 0061FF1C)
// this prints out the value stored at 0061FF1C which is 5
printf("x: %d\n", 5);
// this FIRST gets the value stored at 0061FF1C which is 5
// then increments it so thus 6 is now stored at 0061FF1C
(5)++;
void main()
int x = 5;
// this is an assumed address for x
int *px = 0061FF1C;
/*so far px is a pointer with the address lets say 0061FF24 which holds
*the value 0061FF1C, when passing px to f we are passing by value...
*thus 0061FF1C is passed in (NOT THE POINTER BUT THE VALUE IT HOLDS!)
*/
f(px);
/*this prints out the value stored at the address of x (0061FF1C)
*which is now 6
*/
printf("x: %d",6);
我对指针的主要误解是按值传递与按引用传递。原始指针根本没有传递给函数,所以我们不能改变它指向的地址,只能改变新指针的地址(它有一个旧指针的错觉,因为它指向旧指针的地址指向!)。
【讨论】:
【参考方案2】:问:这是什么(**)?
A:是的,就是这样。一个指针 指针。
问:它有什么用?
答:它有很多用途。特别是在表示二维数据(图像等)方面。在您的示例中,char** argv
可以被认为是char
s 数组的数组。在这种情况下,每个char*
都指向一个字符串的开头。您实际上可以像这样自己明确声明这些数据。
char* myStrings[] =
"Hello",
"World"
;
char** argv = myStrings;
// argv[0] -> "Hello"
// argv[1] -> "World"
当您访问像数组这样的指针时,您索引它的数字和元素本身的大小用于偏移到数组中下一个元素的地址。你也可以像这样访问你所有的号码,事实上这基本上就是 C 正在做的事情。请记住,编译器知道像int
这样的类型在编译时使用了多少字节。所以它知道每一步到下一个元素应该有多大。
*(numbers + 0) = 1, address 0x0061FF1C
*(numbers + 1) = 3, address 0x0061FF20
*(numbers + 2) = 4, address 0x0061FF24
*(numbers + 3) = 5, address 0x0061FF28
*
运算符称为解引用运算符。它用于从指针指向的内存中检索值。 numbers
实际上只是指向数组中第一个元素的指针。
在我的示例中,myStrings
可能看起来像这样,假设指针/地址是 4 个字节,这意味着我们在 32 位机器上。
myStrings = 0x0061FF14
// these are just 4 byte addresses
(myStrings + 0) -> 0x0061FF14 // 0 bytes from beginning of myStrings
(myStrings + 1) -> 0x0061FF18 // 4 bytes from beginning of myStrings
myStrings[0] -> 0x0061FF1C // de-references myStrings @ 0 returning the address that points to the beginning of 'Hello'
myStrings[1] -> 0x0061FF21 // de-references myStrings @ 1 returning the address that points to the beginning of 'World'
// The address of each letter is 1 char, or 1 byte apart
myStrings[0] + 0 -> 0x0061FF1C which means... *(myStrings[0] + 0) = 'H'
myStrings[0] + 1 -> 0x0061FF1D which means... *(myStrings[0] + 1) = 'e'
myStrings[0] + 2 -> 0x0061FF1E which means... *(myStrings[0] + 2) = 'l'
myStrings[0] + 3 -> 0x0061FF1F which means... *(myStrings[0] + 3) = 'l'
myStrings[0] + 4 -> 0x0061FF20 which means... *(myStrings[0] + 4) = 'o'
【讨论】:
"char** argv
可以被认为是char
s 数组的数组。" 不,指针不是数组。 char**
是指向 char
的指针。就是这样。
@alk 我从来没有说过指针是一个数组,我说“指针可以被认为是一个数组”,因为从用户的角度来看,它们可以类似地进行交互,主要是通过下标运算符。之所以做这个比较,是因为刚接触指针的新手,往往首先对数组有一个了解,因为有相似之处,所以很有帮助。【参考方案3】:
首先,请记住,C 对数组的处理与 Java 非常不同。像
这样的声明char foo[10];
为 10 个char
值分配足够的存储空间,没有别的(取模任何额外的空间以满足对齐要求);没有为指向第一个元素的指针或任何其他类型的元数据(例如数组大小或元素类类型)预留额外的存储空间。除了数组元素本身1之外,没有对象foo
。相反,语言中有一条规则,只要编译器看到数组 expression 不是 sizeof
或一元 &
运算符的操作数(或用于初始化另一个数组的字符串文字)在声明中),它隐式地将表达式从类型“T
的N元素数组”转换为“指向T
的指针”,并且表达式的值是第一个的地址数组的元素。
这有几个含义。首先是当你将数组表达式作为参数传递给函数时,函数实际接收的是一个指针值:
char foo[10];
do_something_with( foo );
...
void do_something_with( char *p )
...
实参foo
对应的形参p
是指向char
的指针,而不是char
的数组。为了让事情变得混乱,C 允许将do_something_with
声明为
void do_something_with( char p[] )
甚至
void do_something_with( char p[10] )
但在函数参数声明的情况下,T p[]
和T p[N]
与T *p
相同,并且所有三个都将p
声明为指针,而不是数组2。请注意,这仅适用于函数参数声明。
第二个含义是下标运算符[]
可以用在指针操作数上,也可以用在数组操作数上,比如
char foo[10];
char *p = foo;
...
p[i] = 'A'; // equivalent to foo[i] = 'A';
最后的含义导致处理指向指针的指针的一种情况-假设您有一个指针数组,例如
const char *strs[] = "foo", "bar", "bletch", "blurga", NULL ;
strs
是const char *
的5元素数组3;但是,如果你将它传递给像
do_something_with( strs );
那么函数接收的实际上是一个指向指针的指针,而不是一个指针数组:
void do_something_with( const char **strs ) ...
指向指针的指针(和更高级别的间接)也出现在以下情况:
写入指针类型的参数: 请记住,C 通过值传递所有参数;函数定义中的形参与函数调用中的实参在内存中是不同的对象,所以如果想让函数更新实参的值,必须传递一个指针到那个参数:void foo( T *param ) // for any type T
*param = new_value(); // update the object param *points to*
void bar( void )
T x;
foo( &x ); // update the value in x
现在假设我们将类型T
替换为指针类型R *
,那么我们的sn-p代码如下所示:void foo( R **param ) // for any type R *
...
*param = new_value(); // update the object param *points to*
...
void bar( void )
R *x;
foo( &x ); // update the value in x
相同的语义 - 我们正在更新 x
中包含的值。只是在这种情况下,x
已经有了指针类型,所以我们必须将指针传递给指针。这可以扩展到更高层次的方向:void foo( Q ****param ) // for any type Q ***
...
*param = new_value(); // update the object param *points to*
...
void bar( void )
Q ***x;
foo( &x ); // update the value in x
动态分配的多维数组:在 C 中分配多维数组的一种常用技术是分配一个指针数组,并为该数组的每个元素分配一个指针指向的缓冲区: T **arr;
arr = malloc( rows * sizeof *arr ); // arr has type T **, *arr has type T *
if ( arr )
for ( size_t i = 0; i < rows; i++ )
arr[i] = malloc( cols * sizeof *arr[i] ); // arr[i] has type T *
if ( arr[i] )
for ( size_t j = 0; j < cols; j++ )
arr[i][j] = some_initial_value();
这可以扩展到更高级别的间接性,因此您有 T ***
和 T ****
等类型。
1. 这就是为什么数组表达式可能不是赋值目标的部分原因;没有什么可以分配任何东西到。
这是从衍生出 C 的 B 编程语言的保留;在 B 中,指针被声明为auto p[]
。
每个字符串字面量都是char
的数组,但是因为我们没有使用它们来初始化char
的各个数组,所以表达式被转换为指针值。
【讨论】:
【参考方案4】:**
在声明中表示指向指针的指针。指针本身就是一种数据类型,与其他数据类型一样,它可以有一个指针。
int i = 5, j = 6; k = 7;
int *ip1 = &i, *ip2 = &j;
int **ipp = &ip1;
指针在分配动态二维数组的情况下很有用。分配一个 10x10 的二维数组(可能不连续)
int **m = malloc(sizeof(int *)*10;
for(int i = 0; i < 10; i++)
m[i] = malloc(sizeof(int)*10
当您想通过函数更改指针的值时也使用它。
void func (int **p, int n)
*p = malloc(sizeof(int)*n); // Allocate an array of 10 elements
int main(void)
int *ptr = NULL;
int n = 10;
func(&ptr, n);
if(ptr)
for(int i = 0; i < n; i++)
ptr[i] = ++i;
free(ptr);
延伸阅读:Pointer to Pointer.
【讨论】:
"指向指针的指针在分配动态二维数组的情况下很有用" ...如果您需要从不同的范围更改指针(例如在不同的函数中) @tinky_winky;是的。添加到答案中。【参考方案5】:**
代表指向指针的指针,正如您所知道的名称。我会解释你的每一个问题:
这是什么(**)?
指针指向指针。有时人们称之为双指针。例如:
int a = 3;
int* b = &a; // b is pointer. stored address of a
int**b = &b; // c is pointer to pointer. stored address of b
int***d = &c; // d is pointer to pointer to pointer. stored address of d. You get it.
它在内存中是如何表示的?
c
在上面的例子中只是一个普通变量,与其他变量(指针,int ...)具有相同的表示。变量 c 的内存大小与b
相同,它取决于平台。例如,32 位计算机,每个变量地址包含 32 位,因此大小为 4 字节(8x4=32 位) 在 64 位计算机上,每个变量地址为 64 位,因此大小为 8 字节(8x8=64 位)。
它有什么用?
指针有很多用法,取决于你的情况。例如,这是我在算法课上学到的一个例子。你有一个链表。现在,您想编写一个方法来更改该链表,并且您的方法可能会更改链表的头部。 (例如:删除一个值为 5 的元素,删除头元素,交换,...)。所以你有两种情况:
1.如果你只是传递一个头元素的指针。也许那个 head 元素会被移除,并且这个指针不再有效。
2。如果你传递头元素指针的指针。 如果你的头元素被删除,你不会遇到问题,因为指针的指针仍然存在。它只是改变另一个头节点的值。
上面的例子可以参考这里:pointer to pointer in linked list
另一种用法是在二维数组中使用。 C与Java不同。 C中的二维数组,实际上只是一个连续的内存块。 Java中的二维数组是多内存块(取决于你的矩阵行)
希望有帮助:)
【讨论】:
你的意思是:intc = &b; // c 是指向指针的指针。 b int*d = &c; 的存储地址// d 是指向指针的指针。 c的存储地址。你明白了。【参考方案6】:在 C 中,参数是按值传递的。例如,如果您在 main 中有一个整数变量
int main( void )
int x = 10;
//...
还有下面的函数
void f( int x )
x = 20;
printf( "x = %d\n", x );
那么如果你像这样在main中调用函数
f( x );
然后参数获取main中变量x
的值。但是,参数本身在内存中所占的范围与参数不同。所以函数中参数的任何变化都不会影响main中的原始变量,因为这些变化发生在不同的内存范围内。
那么如何改变函数中main中的变量呢?
您需要使用指针传递对变量的引用。
在这种情况下,函数声明如下所示
void f( int *px );
函数定义为
void f( int *px )
*px = 20;
printf( "*px = %d\n", *px );
在这种情况下,原始变量 x
占用的内存范围发生了变化,因为在函数中我们可以使用指针访问这个范围
*px = 20;
这个函数当然必须在main中调用
f( &x );
考虑到作为指针px
的参数本身通常是函数的局部变量。也就是函数创建这个变量,并用变量x
的地址初始化它。
现在让我们假设你在 main 中声明了一个指针,例如以下方式
int main( void )
int *px = malloc( sizeof( int ) );
//..
和定义的函数类似
void f( int *px )
px = malloc( sizeof( int ) );
printf( "px = %p\n", px );
由于参数px
是一个局部变量,分配给它的任何值都不会影响原始指针。该函数更改的内存范围与main中原始指针px
所占用的范围不同。
如何改变函数中原有的指针? 只是通过引用传递它!
例如
f( &px );
//...
void f( int **px )
*px = malloc( sizeof( int ) );
printf( "*px = %p\n", *px );
在这种情况下,存储在原始指针中的值将在函数内更改,因为使用解引用的函数访问的内存范围与定义原始指针的内存范围相同。
【讨论】:
那么指向指针(**)的指针正在存储另一个指针的内存地址? @JúliusMarko 是的,你是对的。指向指针的指针的值是其他指针的地址。【参考方案7】:实际上,在 C 中,数组是指针:
char* string = "HelloWorld!";
相当于:char string[] = "HelloWorld";
而这个:char** argv
就像你所说的“指向指针的指针”。
可以看成一个字符串数组,即多个字符串。但请记住,字符串是字符指针!
参见:Java 中的 main 方法类似于 C 中的 main 函数。是这样的:
public static void main(String[] args)
即字符串数组。它在 C 中的工作方式相同,String[] args
变为 char** args
或 char* args[]
。
总结一下:type* name = blablabla;
可能是一个“类型”数组。
而type** name = blabla;
可能是一个数组数组。
【讨论】:
C 数组是 not 指针。两者有着密切的联系,但重要的是要了解它们根本不是一回事。【参考方案8】:编写argv
参数的传统方式是char *argv[]
,它提供了更多关于它是什么的信息,一个字符指针数组(即字符串数组)。
但是,当将数组传递给函数时,它会衰减为指针,从而为您留下指向 char
或 char **
的指针。
当然,在取消引用指向指针的指针时也可以使用双星号,因此在问题末尾没有添加上下文的情况下,根据上下文,**
在 C 中的含义有两个答案。
继续argv
示例,获取argv
中第一个元素的第一个字符的一种方法是执行argv[0][0]
,或您可以使用解引用运算符两次,如**argv
。
数组索引和解引用在大多数地方是可以互换的,因为对于任何指针或数组p
和索引i
,表达式p[i]
等价于*(p + i)
。如果i
是0
,那么我们有*(p + 0)
可以缩短为*(p)
,它与*p
相同。
出于好奇,因为p[i]
等价于*(p + i)
和commutative property 的加法,所以表达式*(p + i)
等于*(i + p)
导致p[i]
等于i[p]
。
最后是关于过度使用指针的警告,您可能有时会听到短语three-star programmer,即在***
中使用三个星号(就像在指向指针的指针中一样)。但是要引用链接
要明确一点:被称为三星级程序员通常不是恭维
还有另一个警告:An array of arrays is not the same as a pointer to a pointer(链接到我的旧答案,它还显示了指向指针的指针的内存布局,而不是数组数组。)
【讨论】:
【参考方案9】:它是一个指向指针的指针。如果您问为什么要使用指向指针的指针,这里有一个类似的线程,它以各种好的方式回答了这个问题。
Why use double pointer? or Why use pointers to pointers?
【讨论】:
【参考方案10】:你说的 int *
例子
我猜它的大小是 4 个字节?
与 Java 不同,C 不指定其数据类型的确切大小。不同的实现可以并且确实使用不同的大小(但每个实现必须是一致的)。现在 4 字节的 int
s 很常见,但 int
s 可以小到两个字节,并且没有什么固有的将它们限制为四个。指针的大小甚至更少指定,但它通常取决于 C 实现所针对的硬件架构。最常见的指针大小是 4 个字节(32 位架构的典型)和 8 个字节(64 位架构的常见)。
这是什么(**)?
在您提供的上下文中,它是类型指示符char **
的一部分,正如您所想的那样,它描述了一个指向指向char
的指针。
它有什么用?
与指向任何其他数据类型的指针大致相同。有时您希望或需要间接访问指针值,就像您可能希望或需要间接访问任何其他类型的值一样。此外,它对于指向指针数组(的第一个元素)很有用,这就是它在 C main()
函数的第二个参数中的使用方式。
在这种特殊情况下,指向数组中的每个char *
本身都指向程序的命令行参数之一。
它在内存中是如何表示的?
C 没有指定,但通常指向指针的指针与指向任何其他类型值的指针具有相同的表示。它指向的值只是一个指针值。
【讨论】:
【参考方案11】:考虑一下您是否有一个指针表 - 例如一个字符串表(因为“C”中的字符串被简单地作为指向字符串第一个字符的指针来处理)。
那么你需要一个指向表中第一个指针的指针。因此是“char **”。
如果你有一个包含所有值的内联表,比如一个二维整数表,那么完全有可能只使用一个间接级别(即,只是一个简单的指针,比如“int *”)。但是当中间有一个指针需要解引用才能得到最终结果时,这就创建了第二级间接,那么指针指向就必不可少了。
这里的另一个澄清。在“C”中,通过指针表示法(例如“*ptr”)与数组索引表示法(例如ptr [0])解引用几乎没有区别,除了数组表示法中明显的索引值。星号与括号真正重要的唯一时间是分配变量时(例如 int *x; 与 int x[1] 非常不同)。
【讨论】:
【参考方案12】:我会将char **argv
理解为char** argv
。现在,char*
基本上是char
的数组,所以(char*)*
是char
的数组。
换句话说,argv
是一个字符串数组。在这个特定的例子中:调用
myExe dummyArg1 dummyArg2
在控制台中会将argv
设为
argv[0] = "myExe"
argv[1] = "dummyArg1"
argv[2] = "dummyArg2"
【讨论】:
它不是数组数组。这将是 [][],具有固定的内存布局。 @TomKarzes 我的 C++ 不是来自书本,主要来自实践。所以如果我错了请纠正我。但我认为我的观点是有效的,如下运行:long long a[3]=1,1,1;cout<< ((int*)a)[1]<<"\n";
并返回 0
。
我的评论不是很准确。我的观点是,声明器中的 *
字符与声明的各个项目相关联,而不是与***数据类型相关联。所以char **x
实际上是char (*(*x))
。这就是它的解析和解释方式。同样,如果写char* x, y
,它实际上是char (*x), y
,即y
的类型为char
,而不是char *
。
@TomKarzes 谢谢,我明白你现在的意思并学到了新东西。不过,我认为char *x,y;
是一种不好的做法-)。
"char*
基本上是char
的数组,所以(char*)*
是char
的数组数组。" No and No. 指针不是数组。【参考方案13】:
例如,** 是指向指针的指针。 char **argv
与 char *argv[]
相同,这与 char argv[][]
相同。这是一个矩阵。
例如,您可以声明一个包含 4 行但列数不同的矩阵,例如 JaggedArrays。
它表示为一个矩阵。
Here你在内存中有一个表示。
【讨论】:
"它是一个矩阵。" 不,char**
是一个指向char
的指针,仅此而已。【参考方案14】:
**
表示指向指针的指针。如果你想通过引用传递参数,你会使用*
,但是如果你想通过引用传递指针本身,那么你需要一个指向指针的指针,因此**
。
【讨论】:
以上是关于**在C语言中是做啥的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章