**用C语言做什么

时间:2016-01-26 18:49:40

标签: c arrays pointers memory

我是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];

看起来像这样:

pointer to numbers

而我猜它的大小是4字节?

然而**我读作"指向指针的指针"这对我来说没有意义,为什么有人想要指针指针,当然如果a-> b>> c然后a-> c就足够了?我知道我错过了某些内容,而且它必须与数组有关,因为argv可以是char[ ]char **类型,如下所示:

int main(int argc, char **argv){}

所以:

  • 这是什么(**)?
  • 它有什么用途?
  • 它在记忆中的表现如何?

14 个答案:

答案 0 :(得分:31)

在C中,参数由值传递。例如,如果你在主

中有一个整数变量
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 );
}

在这种情况下,原始指针中存储的值将在函数内更改,因为使用解除引用的函数访问定义原始指针的相同内存范围。

答案 1 :(得分:10)

问:这是什么(**)?

答:是的,确实如此。指向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'

答案 2 :(得分:8)

编写argv参数的传统方法是char *argv[],它提供了有关它是什么的更多信息,一个指向字符的指针数组(即一个字符串数组)。

但是,当一个数组传递给一个函数时,它会衰减到一个指针,给你一个指向charchar **指针的指针。

当然,在解除引用指针的指针时也可以使用双星号,因此在问题结尾没有添加上下文的情况下,{C}中**的含义有两个答案,具体取决于上下文。

要继续argv示例,获取argv中第一个元素的第一个字符的一种方法是argv[0][0]你可以使用解除引用运算符两次,如**argv

在大多数地方,数组索引和解除引用是可以互换的,因为对于任何指针数组p和索引i,表达式p[i]等同于{{ 1}}。如果*(p + i)i,那么我们0可以缩短为*(p + 0),与*(p)相同。

由于好奇心,因为*p相当于p[i]commutative property的加法,所以表达式*(p + i)等于*(p + i),导致*(i + p) {1}}等于p[i]

最后一个关于过度使用指针的警告,你有时可能会听到短语three-star programmer,这就是当你使用i[p]中的三个星号时(就像指向指针的指针一样) 。但引用链接

  

需要明确的是:被称为三星程序员通常不是一种恭维

另一个警告:An array of arrays is not the same as a pointer to a pointer(链接到我的旧答案,它还显示指向指针的指针的内存布局,作为数组数组的替代。)

答案 3 :(得分:5)

声明中的

classes.jar表示指向指针的指针。指针本身就是一种数据类型,就像其他数据类型一样,它可以有一个指针。

**

enter image description here

指向指针的指针在分配动态2D数组时很有用。分配10x10 2D阵列(可能不连续)

int i = 5, j = 6; k = 7;
int *ip1 = &i, *ip2 = &j; 
int **ipp = &ip1;  

当您想通过函数更改指针的值时,也会使用它。

int **m = malloc(sizeof(int *)*10;  
for(int i = 0; i < 10; i++)
    m[i] = malloc(sizeof(int)*10  

进一步阅读:Pointer to Pointer

答案 4 :(得分:4)

考虑一下你是否有一个指针表 - 比如一个字符串表(因为&#34; C&#34;中的字符串只是作为指向字符串第一个字符的指针)。

然后你需要一个指向表中第一个指针的指针。因此,&#34; char **&#34;。

如果你有一个包含所有值的内联表,比如一个二维的整数表,那么它完全有可能只用一级间接(即只是一个简单的指针,如&#) 34; int *&#34;)。但是当中间有一个指针需要被解除引用以获得最终结果时,这会创建第二级间接,然后指向指针是必不可少的。

这里有另一个澄清。在&#34; C&#34;中,通过指针表示法(例如&#34; * ptr&#34;)与数组索引表示法(例如ptr [0])的解除引用几乎没有区别,除了数组表示法中明显的索引值。星号和括号唯一真正重要的是分配变量时(例如int * x;与int x [1]非常不同)。

答案 5 :(得分:4)

你说的int *例子

  

而我猜它的大小是4字节?

与Java不同,C不指定其数据类型的确切大小。不同的实现可以并且确实使用不同的大小(但每个实现必须一致)。这些天,4字节int是常见的,但int s可以是两个小字节,并且没有任何内容将它们限制为四个。指针的大小甚至更少指定,但它通常取决于C实现的目标硬件架构。最常见的指针大小是四个字节(32位架构的典型值)和八个字节(64位架构通用)。

  

这是什么(**)?

在您提供的上下文中,它是类型指示符char **的一部分,它描述了指向char指针的指针,正如您所想的那样。

  

它有什么用途?

或多或少地用作指向任何其他数据类型的指针。有时您希望或需要间接访问指针值,就像您可能想要或需要间接访问任何其他类型的值一样。此外,它对于指向指针数组(的第一个元素)非常有用,它是在第二个参数中用于C main()函数的方式。

在这种特殊情况下,指向数组中的每个char *都指向程序的一个命令行参数。

  

它在内存中如何表现?

C没有指定,但通常指向指针的指针与指向任何其他类型值的指针具有相同的表示形式。它指向的值只是一个指针值。

答案 6 :(得分:4)

**代表指针,因为你知道名字。我会解释你的每一个问题:

  

这是什么(**)?

指针指针。有时人们会调用双指针。例如:

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只是一个普通变量,与其他变量(pointer,int ...)具有相同的表示形式。变量c的内存大小与b相同,具体取决于平台。例如,32位计算机,每个变量地址包括32位,因此大小将为4字节(8x4 = 32位)在64位计算机上,每个变量地址将为64位,因此大小将为8字节(8x8 = 64位)。

  

它有什么用途?

指针指针有很多用法,具体取决于你的情况。例如,这是我在算法类中学到的一个例子。你有一个链表。现在,您想要编写一个方法来更改该链接列表,并且您的方法可能会更改链接列表的头部。 (示例:删除一个值等于5的元素,删除head元素,swap,...)。所以你有两种情况:

<强> 1。如果你只传递一个head元素指针。也许该head元素将被删除,并且此指针不再有效。

<强> 2。如果你传递head元素指针的指针。如果你的head元素被删除,你就不会遇到任何问题,因为指针指针仍在那里。它只是改变另一个头节点的值。

您可以在此处参考以上示例:pointer to pointer in linked list

另一种用法是在二维数组中使用。 C与Java不同。 C中的二维数组,实际上只是一个连续的内存块。 Java中的二维数组是多内存块(取决于你的矩阵行)

希望这有帮助:)

答案 7 :(得分:4)

首先,请记住C对待数组的方式与Java截然不同。像

这样的声明
char foo[10];

为10 char个值分配足够的存储空间为其他分配任何额外的空间以满足对齐要求;没有为指向第一个元素或任何其他类型的元数据(如数组大小或元素类类型)的指针留出额外的存储空间。除了数组元素本身 1 之外,没有对象foo。相反,在语言中有一条规则,即编译器看到的数组表达式,它不是sizeof或一元&运算符的操作数(或者用于初始化声明中的另一个数组的字符串文字,它隐式地转换表达式来自类型&#34; N元素数组T&#34; to&#34;指向T&#34;的指针,表达式的值是数组第一个元素的地址。

这有几个含义。首先,当您将数组表达式作为参数传递给函数时,函数实际接收的是指针值:

char foo[10];
do_something_with( foo );
...
void do_something_with( char *p )
{
  ...
}

与实际参数p对应的形式参数foo是指向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 };

strsconst char * 3 的5元素数组;但是,如果你把它传递给像

这样的函数
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 *,那么我们的代码片段如下所示:

    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. 这是来自B编程语言的延续,C语言来源于此;在B中,指针被声明为auto p[]

  2. 每个字符串文字都是char的数组,但由于我们没有使用它们来初始化char的各个数组,因此表达式将转换为指针值。

答案 8 :(得分:3)

**表示指向指针的指针。如果你想通过引用传递一个参数,你可以使用*,但如果你想通过引用传递指针本身,那么你需要一个指向指针的指针,因此{{ 1}}。

答案 9 :(得分:3)

它是指向指针的指针。如果你问为什么你想要使用指针指针,这里有一个类似的线程,以各种好的方式回答它。

Why use double pointer? or Why use pointers to pointers?

答案 10 :(得分:3)

我想我会在这里添加我自己的答案,以及每个人都做了一个了不起的工作,但我真的很困惑指针的指针是什么。我想出这个的原因是因为我的印象是除了指针之外的所有值都是通过值传递的,而指针是通过引用传递的。请参阅以下内容:

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);
}

我对指针指针的主要误解是传递值与传递引用。根本没有将原始指针传递给函数,因此我们无法更改它所指向的地址,只能更改新指针的地址(它具有旧指针的错觉,因为它指向旧指针的地址)指向!)。

答案 11 :(得分:1)

我理解char **argvchar** argv。现在,char*基本上是char的数组,因此(char*)*char数组的数组。

在其他(松散)单词中,argv是一个字符串数组。在这个特定的例子中:呼叫

myExe dummyArg1 dummyArg2
在控制台中

argv设为

argv[0] = "myExe"
argv[1] = "dummyArg1"
argv[2] = "dummyArg2"

答案 12 :(得分:1)

例如,**是指向指针的指针。 char **argvchar *argv[]相同,与char argv[][]相同。它是一个矩阵。

例如,您可以声明一个包含4行的矩阵,但列数不同,例如JaggedArrays。

它表示为矩阵。

Here你在记忆中有表现。

答案 13 :(得分:-4)

实际上,在C数组中有指针:

char* string = "HelloWorld!";

相当于:char string[] = "HelloWorld"; 而且:char** argv正如你所说的“指向指针”。

它可以看作是一个字符串数组,即多个字符串。但请记住,字符串是char指针!

参见:在Java中,main方法类似于C main函数。它是这样的:

public static void main(String[] args){}

即一个字符串数组。它在C中的工作方式相同,String[] args变为char** argschar* args[]

总结:type* name = blablabla; 可能是一个“类型”数组。 type** name = blabla;可能是一个数组数组。