如何从c中的字符串数组中访问单个字符?

时间:2009-03-03 15:38:25

标签: c arrays pointers string

试图了解如何处理字符串数组中的单个字符。此外,这当然会让我理解指向一般订阅的指针。 如果我有char **a并且我想要达到第二个字符串的第3个字符,那么这是否有效:**((a+1)+2)?好像它应该......

6 个答案:

答案 0 :(得分:18)

几乎,但不完全。正确答案是:

*((*(a+1))+2)

因为您需要首先取消引用其中一个实际字符串指针,然后将所选字符串指针取消引用到所需字符。 (请注意,为了清晰起见,我添加了额外的括号)。

或者,这个表达式:

a[1][2]

也会起作用!....也许会更受欢迎,因为你想要做的事情的意图更加明显,而且符号本身更简洁。对于刚接触该语言的人来说,这种形式可能不会立即显而易见,但要理解数组符号的工作原因是因为在C中,数组索引操作实际上只是等效指针操作的简写。即:*(a + x)与[x]相同。因此,通过将该逻辑扩展到原始问题,有两个单独的指针解引用操作级联在一起,其中表达式a [x] [y]等价于*((*(a + x))+的一般形式Y)。

答案 1 :(得分:3)

您不必使用指针。

  

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

     

printf(“的第三个字符   argv [1]是[%c]。\ n“,argv [1] [2]);

     

}

然后:

  

$ ./main hello。的第三个字符   argv [1]是[l]。

这是一个和一个。

如果你想要你可以使用指针......

  

*(argv [1] +2)

甚至

  

*((*(A + 1))+ 2)

正如有人指出的那样。

这是因为数组名称是指针。

答案 2 :(得分:2)

Iirc,一个字符串实际上是一个字符数组,所以这应该有效:

a[1][2]

答案 3 :(得分:2)

从维基百科article引用C指针 -

在C中,数组索引是根据指针算法正式定义的;那是, 语言规范要求array [i]等效于*(array + i)。因此在C中,数组可以被认为是指向连续内存区域的指针(没有间隙), 访问数组的语法与可用于解除引用的语法相同 指针。例如,可以按以下方式声明和使用数组:

int array[5];      /* Declares 5 contiguous (per Plauger Standard C 1992) integers */
int *ptr = array;  /* Arrays can be used as pointers */
ptr[0] = 1;        /* Pointers can be indexed with array syntax */
*(array + 1) = 2;  /* Arrays can be dereferenced with pointer syntax */

所以,在回答你的问题 - 时,指针的指针可以用作数组,而不需要任何其他声明!

答案 4 :(得分:2)

在Jon Erickson的第二版“黑客攻击艺术”一书中,有一篇精彩的C编程解释,讨论了指针,字符串,值得一提的编程解释部分https://leaksource.files.wordpress.com/2014 /08/hacking-the-art-of-exploitation.pdf。

虽然这个问题已经得到解答,但是其他想要了解更多信息的人可能会发现Erickons书中的以下要点有助于理解问题背后的一些结构。

<强>接头

可用于变量操作的头文件示例。

stdio.h - http://www.cplusplus.com/reference/cstdio/

stdlib.h - http://www.cplusplus.com/reference/cstdlib/

string.h - http://www.cplusplus.com/reference/cstring/

limits.h - http://www.cplusplus.com/reference/climits/

<强>功能

您可能会使用的通用功能示例。

malloc() - http://www.cplusplus.com/reference/cstdlib/malloc/

calloc() - http://www.cplusplus.com/reference/cstdlib/calloc/

strcpy() - http://www.cplusplus.com/reference/cstring/strcpy/

<强>内存

&#34; 编译程序的内存分为五个部分:文本,数据,bss,堆和堆栈。每个段表示为特定目的而留出的特殊内存部分。文本段有时也称为代码段。这是程序的组装机器语言指令所在的位置&#34;。

&#34; 由于前面提到的高级控制结构和函数,这段中的指令执行是非线性的。 用汇编语言分支,跳转和调用指令。作为一个程序 执行时,EIP被设置为文本段中的第一条指令。该 然后处理器遵循执行以下操作的执行循环:&#34;

&#34;的 1。读取EIP指向的指令&#34;

&#34;的 2。将指令的字节长度添加到EIP &#34;

&#34;的 3。执行步骤1中读取的指令&#34;

&#34;的 4。回到第1步&#34;

&#34; 有时指令将是跳转或调用指令 将EIP更改为不同的内存地址。处理器没有 关心变化,因为它期望执行是非线性的 无论如何。如果在步骤3中更改了EIP,则处理器将返回步骤1并读取在EIP被更改为&#34;的地址中找到的指令。

&#34; 在文本段中禁用写入权限,因为它不用于存储变量,仅用于存储代码。这可以防止人们实际修改程序代码;任何写入这段内存的尝试都会导致程序提醒用户发生了不好的事情和程序 将被杀死。该段只读的另一个优点是它 可以在程序的不同副本之间共享,允许多个 同时执行该程序没有任何问题。这应该 还要注意的是,这个内存段有一个固定的大小,因为什么都没有 它的变化&#34;。

&#34; 数据和bss段用于存储全局和静态程序 变量。数据段用初始化的全局变量和静态变量填充,而bss段用未初始化的对应物填充。尽管这些段是可写的,但它们也具有固定的大小。请记住,尽管存在功能上下文(如前面示例中的变量j),但全局变量仍然存在。全局变量和静态变量都能够持久化,因为它们存储在自己的内存段中&#34;。

&#34; 堆段是程序员可以直接访问的一段内存 控制。可以分配和使用此段中的内存块 无论程序员可能需要什么。关于堆的一个值得注意的观点 段是不是固定大小,因此它可以根据需要增大或减小&#34;。

&#34; 堆中的所有内存都由allocator和deallocator算法管理,这些算法分别保留堆中的内存区域以供使用,并删除保留以允许重用该部分内存以后的预订。堆将根据方式增长和缩小 大量内存保留使用。这意味着程序员使用堆 分配功能可以动态保留和释放内存。的增长 堆向下移向更高的内存地址&#34;。

&#34; 堆栈段也具有可变大小,并用作临时暂存区,用于在函数调用期间存储本地函数变量和上下文。这就是GDB的backtrace命令所关注的。当程序调用一个函数时,该函数将拥有自己的一组传递变量,函数的代码将位于文本(或代码)段中的不同内存位置。由于上下文和EIP必须在调用函数时发生更改,因此堆栈用于记住所有传递的变量,EIP在函数完成后应返回的位置以及该函数使用的所有局部变量。所有这些信息一起存储在堆栈中,统称为堆栈帧。堆栈包含许多堆栈帧&#34;。

&#34; 在一般的计算机科学术语中,堆栈是经常使用的抽象数据结构。它具有先进先出(FILO)排序 ,这意味着放入堆栈的第一个项目是最后一个项目。可以把它想象成将珠子放在一根在一端有结的绳子上 - 在你移除所有其他珠子之前,你不能得到第一个珠子。当一个项目被放入堆栈时,它被称为推送,当一个项目从堆栈中移除时,它被称为弹出&#34;。

&#34; 顾名思义,内存的堆栈段实际上是一个堆栈数据结构,它包含堆栈帧。 ESP寄存器用于跟踪堆栈末尾的地址,当物品被推入和弹出时,它会不断变化。由于这是非常动态的行为,因此堆栈也不是固定大小。与堆的动态增长相反,随着堆栈的变化 在大小上,它在内存的可视列表中向上增长,朝向较低的内存地址&#34;。

&#34; 堆栈的FILO性质可能看起来很奇怪,但是因为堆栈被使用了 存储上下文,它非常有用。调用函数时,会在堆栈框架中将多个内容一起推送到堆栈。 EBP寄存器 - 有时称为帧指针(FP)或本地基(LB)指针 - 用于引用当前堆栈帧中的本地函数变量。每个堆栈帧都包含函数的参数,它的局部变量,以及两个指针,这些指针是将它们放回原来所需要的:保存的帧指针(SFP)和返回地址。该 SFP用于将EBP恢复到其先前的值和返回地址 用于将EIP恢复到函数调用后找到的下一条指令。这将恢复先前堆栈的功能上下文 帧的&#34;

<强>字符串

&#34; 在C中,数组只是特定数据类型的n个元素的列表。一个20个字符的数组就是位于内存中的20个相邻字符。数组也称为缓冲区&#34;。

#include <stdio.h>

int main()
{
    char str_a[20];
    str_a[0] = 'H';
    str_a[1] = 'e';
    str_a[2] = 'l';
    str_a[3] = 'l';
    str_a[4] = 'o';
    str_a[5] = ',';
    str_a[6] = ' ';
    str_a[7] = 'w';
    str_a[8] = 'o';
    str_a[9] = 'r';
    str_a[10] = 'l';
    str_a[11] = 'd';
    str_a[12] = '!';
    str_a[13] = '\n';
    str_a[14] = 0;
    printf(str_a);
}

&#34; 在前面的程序中,20个元素的字符数组被定义为 str_a,并逐个写入数组的每个元素。请注意,数字从0开始,而不是1.还要注意最后一个字符是0 &#34;。

&#34; (这也称为空字节。)字符数组已定义,因此为其分配了20个字节,但实际上只使用了12个字节。最后的空字节编程用作分隔符,告诉任何正在处理字符串的函数在那里停止操作。剩下的额外字节只是垃圾,将被忽略。如果在字符数组的第五个元素中插入空字节,则printf()函数&#34;仅打印字符Hello。

&#34; 由于设置字符数组中的每个字符都很费劲并经常使用字符串,因此为字符串操作创建了一组标准函数。例如,strcpy()函数将字符串从源复制到目标,遍历源字符串并将每个字节复制到目标(并在复制空终止字节后停止)&#34;

&#34; 函数参数的顺序首先类似于英特尔汇编语法目标,然后是源。可以使用strcpy()重写char_array.c程序,以使用字符串库完成相同的操作。下面显示的char_array程序的下一个版本包括string.h,因为它使用字符串函数&#34;。

#include <stdio.h>
#include <string.h>

int main() 
{
    char str_a[20];
    strcpy(str_a, "Hello, world!\n");
    printf(str_a);
}

查找有关C字符串的更多信息

http://www.cs.uic.edu/~jbell/CourseNotes/C_Programming/CharacterStrings.html

http://www.tutorialspoint.com/cprogramming/c_strings.htm

<强>指针

&#34; EIP寄存器是一个指针,它通过包含其存储器地址在程序执行期间“指向”当前指令。指针的思想也在C中使用。由于物理内存实际上无法移动,因此必须复制其中的信息。复制大块存储器以供不同功能或不同位置使用可能在计算上非常昂贵。从存储器的角度来看,这也是昂贵的,因为在复制源之前必须保存或分配新目标副本的空间。指针是解决此问题的方法。传递内存块的开头地址&#34;而不是复制大块内存,更简单。

&#34; C中的指针可以像任何其他变量类型一样定义和使用。由于x86架构上的内存使用32位寻址,因此指针的大小也是32位(4字节)。通过在变量名称前加上星号(*)来定义指针。指针定义为指向该类型数据的东西,而不是定义该类型的变量。 pointer.c程序是一个与char数据类型一起使用的指针的示例,它的大小只有1字节&#34;。

#include <stdio.h>
#include <string.h>

int main() 
{
    char str_a[20]; // A 20-element character array
    char *pointer; // A pointer, meant for a character array
    char *pointer2; // And yet another one
    strcpy(str_a, "Hello, world!\n");
    pointer = str_a; // Set the first pointer to the start of the array.
    printf(pointer);
    pointer2 = pointer + 2; // Set the second one 2 bytes further in.
    printf(pointer2); // Print it.
    strcpy(pointer2, "y you guys!\n"); // Copy into that spot.
    printf(pointer); // Print again.
}

&#34; 正如代码中的注释所示,第一个指针设置在字符数组的开头。当像这样引用字符数组时,它实际上是一个指针本身。这是这个缓冲区作为指向printf()和strcpy()函数的指针传递的方式。第二个指针设置为第一个指针地址加上两个,然后打印一些东西(显示在下面的输出中)&#34;。

reader@hacking:~/booksrc $ gcc -o pointer pointer.c
reader@hacking:~/booksrc $ ./pointer
Hello, world!
llo, world!
Hey you guys!
reader@hacking:~/booksrc $

&#34; address-of运算符通常与指针一起使用,因为指针包含内存地址。 addressof.c程序演示 address-of运算符用于放置整数变量的地址 成指针。这一行在下面以粗体显示&#34;。

#include <stdio.h>

int main() 
{
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // put the address of int_var into int_ptr
}

&#34; 存在一个名为dereference运算符的附加一元运算符,用于指针。此运算符将返回指针指向的地址中找到的数据,而不是地址本身。它采用变量名前面的星号形式,类似于指针的声明。再一次,解除引用运算符同时存在于GDB和C &#34;。

&#34; 对addressof.c代码的一些补充(在addressof2.c中显示)将会 展示所有这些概念。添加的printf()函数使用格式 参数,我将在下一节中解释。现在,只关注程序输出&#34;。

#include <stdio.h>

int main() 
{
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // Put the address of int_var into int_ptr.
    printf("int_ptr = 0x%08x\n", int_ptr);
    printf("&int_ptr = 0x%08x\n", &int_ptr);
    printf("*int_ptr = 0x%08x\n\n", *int_ptr);
    printf("int_var is located at 0x%08x and contains %d\n", &int_var, int_var);
    printf("int_ptr is located at 0x%08x, contains 0x%08x, and points to %d\n\n", &int_ptr, int_ptr, *int_ptr);
}

&#34; 当一元运算符与指针一起使用时,地址运算符可以被认为是向后移动,而解除引用运算符在指针指向的方向上向前移动& #34;

了解有关指针和放大器的更多信息内存分配

加州大学计算机科学系Dan Hirschberg教授计算机内存https://www.ics.uci.edu/~dan/class/165/notes/memory.html

http://cslibrary.stanford.edu/106/

http://www.programiz.com/c-programming/c-dynamic-memory-allocation

<强>阵列

这是一个关于多维数组的简单教程,由一位名叫Alex Allain的小伙伴在这里提供http://www.cprogramming.com/tutorial/c/lesson8.html

有关阵列的信息由一个名叫Todd A Gibson的小伙伴在这里提供http://www.augustcouncil.com/~tgibson/tutorial/arr.html

迭代数组

#include <stdio.h>

int main() 
{

    int i;
    char char_array[5] = {'a', 'b', 'c', 'd', 'e'};
    int int_array[5] = {1, 2, 3, 4, 5};
    char *char_pointer;
    int *int_pointer;
    char_pointer = char_array;
    int_pointer = int_array;

    for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.
        printf("[integer pointer] points to %p, which contains the integer %d\n", int_pointer, *int_pointer);
        int_pointer = int_pointer + 1;
    }

    for(i=0; i < 5; i++) { // Iterate through the char array with the char_pointer.
        printf("[char pointer] points to %p, which contains the char '%c'\n", char_pointer, *char_pointer);
        char_pointer = char_pointer + 1;
    }

}

关联列表与数组

数组不是唯一可用的选项,链接列表信息。

http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_linklist.aspx

<强>结论

这些信息仅仅是为了传递我在关于可能对其他人有用的主题的研究中所阅读的一些内容。

答案 5 :(得分:1)

试试a[1][2]。或*(*(a+1)+2)

基本上,数组引用是指针解除引用的语法糖。 a [2]与a + 2相同,也与2 [a]相同(如果你真的想要不可读的代码)。字符串数组与双指针相同。因此,您可以使用[1]或*(a+1)提取第二个字符串。然后,您可以使用b [2]或*(b + 2)找到该字符串中的第三个字符(现在称之为'b')。将原来的第二个字符串替换为'b',我们最终得到[1] [2]或*(*(a+1)+2)