OverIQ 中文系列教程(五)

原文:OverIQ Tutorials

协议:CC BY-NC-SA 4.0

C 语言中的strlen()函数

原文:https://overiq.com/c-programming-101/the-strlen-function-in-c/

最后更新于 2020 年 7 月 27 日


本节讨论 C 语言中字符串库提供的一些常用函数,这些函数是在头文件string.h中声明的,所以在使用这些函数之前,您必须在程序中包含string.h

#include<string.h>

strlen()函数

语法: size_t strlen (const char* str);

**注意:**本章忽略关键字 const。稍后会讨论。

strlen()接受指向char(char*)的类型指针参数,因此您可以传递字符串或字符数组。它返回字符串中除空字符'\0'之外的字符数。回想一下size_t只是unsigned int的别名。

以下是一些例子:

strlen("a string constant"); // returns 17

char arr[] = "an array of characters";
strlen(arr); // returns 22

以下程序计算用户输入的字符串长度。

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

int main()
{
    
    
    int len;

    // destination array can store only 30 characters including '\0'
    char destination[30];

    printf("Enter your dream destination: ");
    gets(destination);

    // calculate length of characters in destination
    len = strlen(destination); 

    printf("Your dream destination %s has %d characters in it", destination, len);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter your dream destination: Bermuda Triangle
Your dream destination Bermuda Triangle has 16 characters in it

我们对弦的一般工作原理有很强的基础。所以让我们创建我们自己版本的strlen()函数。

unsigned int my_strlen(char *p)
{
    
    
    unsigned int count = 0;

    while(*p!='\0')
    {
    
    
        count++;
        p++;
    }

    return count;
}

工作原理:

就像原来的strlen()函数一样,这个函数接受一个指向char(char*)的类型指针的参数,并返回unsigned int。在函数内部,我们声明了一个变量count,并将其初始化为0。while 循环用于计算字符数。每次迭代后,p增加1。当p指向空字符('\0')的地址时,循环停止,变量count的值返回到调用函数。

让我们重写之前的程序,加入my_strlen()函数的定义。

#include<stdio.h>
unsigned int my_strlen(char *p); // function declaration

int main()
{
    
    
    int len;

    // destination array can store only 30 characters including '\0'
    char destination[30];

    printf("Enter your dream destination: ");
    gets(destination);

    // calculate length of characters in destination
    len = my_strlen(destination);

    printf("Your dream destination %s has %d characters in it", destination, len);

    // signal to operating system program ran fine
    return 0;
}

// definition of my_strlen() function

unsigned int my_strlen(char *p)
{
    
    
    unsigned int count = 0;

    while(*p!='\0')
    {
    
    
        count++;
        p++;
    }
    return count;
}

预期输出:

Enter your dream destination: Bermuda Triangle
Your dream destination Bermuda Triangle has 16 characters in it

my_strlen()函数给出的输出与strlen()函数相同,所以我们的函数工作正常。



C 语言中的strcmp()函数

原文:https://overiq.com/c-programming-101/the-strcmp-function-in-c/

最后更新于 2020 年 7 月 27 日


strcmp()函数的语法是:

语法: int strcmp (const char* str1, const char* str2);

strcmp()功能用于比较两根弦两根弦str1str2。如果两个字符串相同,则strcmp()返回0,否则返回非零值。

该函数使用字符的 ASCII 值逐个字符地比较字符串。当到达字符串的任一端或对应的字符不同时,比较停止。不匹配时返回的非零值是两个字符串的不匹配字符的 ASCII 值之差。

让我们通过一个例子来看看strcmp()函数是如何比较字符串的。

strcmp("jkl", "jkq");

这里我们有两条弦str1 = "jkl"str2 = "jkq"。比较开始于比较来自str1str2的第一个字符,即来自"jkl"'j'和来自"jkm"'j',因为它们相等,比较接下来的两个字符,即来自"jkl"'k'和来自"jkm"'k',因为它们也相等,再次比较接下来的两个字符,即来自"jkl"'l'和来自"jkm"'q',因为'q' ( 113)的 ASCII 值大于【的】

需要注意的是,并非所有系统都返回字符 ASCII 值的差值,在某些系统中,如果str1大于str2,则返回1。另一方面,如果str1小于str2,则返回-1。您很可能会在系统中遇到这种行为。

让我们举一些例子:

strcmp("a", "a"); // returns 0 as ASCII value of "a" and "a" are same i.e 97

strcmp("a", "b"); // returns -1 as ASCII value of "a" (97) is less than "b" (98)

strcmp("a", "c"); // returns -1 as ASCII value of "a" (97) is less than "c" (99)

strcmp("z", "d"); // returns 1 as ASCII value of "z" (122) is greater than "d" (100)

strcmp("abc", "abe"); // returns -1 as ASCII value of "c" (99) is less than "e" (101)

strcmp("apples", "apple"); // returns 1 as ASCII value of "s" (115) is greater than "\0" (101)

以下程序比较用户输入的两个字符串。

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

int main()
{
    
    
    char strg1[50], strg2[50];

    printf("Enter first string: ");
    gets(strg1);

    printf("Enter second string: ");
    gets(strg2);

    if(strcmp(strg1, strg2)==0)
    {
    
    
        printf("\nYou entered the same string two times");
    }

    else
    {
    
    
        printf("\nEntered strings are not same!");
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

第一次运行:

Enter first string: compare
Enter second string: compare

You entered the same string two times

第二次运行:

Enter first string: abc
Enter second string: xyz

Entered strings are not same!

带字符串的关系运算符

当关系运算符(><>=<===!=)用于字符串时,它们的行为方式略有不同。考虑以下示例:

char *s1 = "hello";
char *s2 = "yello";

你能猜出下面的表达是什么意思吗?

s1 == s2

这个表达式比较的是s1s2所指向的字符串的地址,而不是字符串文字的内容。

下面的示例演示了这种行为。

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

int main()
{
    
    
    char *s1 = "hello";
    char *s2 = "world";

    printf("Address of string pointed by s1 = %u\n", s1);
    printf("Address of string pointed by s2 = %u\n\n", s2);

    printf("Is s1 == s2 ? %u\n", s1 == s2);
    printf("Is s1 > s2 ? %u\n", s1 > s2);
    printf("Is s1 < s2 ? %u\n", s1 < s2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of string pointed by s1 = 4206592
Address of string pointed by s2 = 4206598

Is s1 == s2 ? 0
Is s1 > s2 ? 0
Is s1 < s2 ? 1

让我们回到最初的讨论,尝试创建我们自己版本的strcmp()函数。

int my_strcmp(char *strg1, char *strg2)
{
    
    
    while( ( *strg1 != '\0' && *strg2 != '\0' ) && *strg1 == *strg2 )
    {
    
    
        strg1++;
        strg2++;
    }

    if(*strg1 == *strg2)
    {
    
    
        return 0; // strings are identical
    }

    else
    {
    
    
        return *strg1 - *strg2;
    }
}

工作原理:

my_strcmp()函数接受两个指向 char 的类型指针参数,并返回一个整数值。while 循环中的条件可能看起来有点吓人,所以让我来解释一下。

( *strg1 != '\0' && *strg2 != '\0' ) && (*strg1 == *strg2)

条件只是说继续循环,直到没有到达字符串的末尾,并且对应的字符相同。

假设调用my_strcmp()有两个参数"abc" ( strg1)和"abz" ( strg2),其中strg1指向地址2000strg2指向地址3000

第一次迭代

在第一次迭代中strg1strg2都指向字符'a'的地址。因此

*strg1返回'a'
*strg2返回'a'

测试条件时:

( 'a' != '\0' && 'a' != '\0' ) && ('a' == 'a')

当条件为真时,执行循环体内部的语句。现在strg1点寻址2001strg2点寻址3001。这将结束第一次迭代。

第二次迭代

在第二次迭代中strg1strg2都指向字符'b'的地址。因此

*strg1返回'b'
*strg2返回'b'

再次测试条件时:

( 'b' != '\0' && 'b' != '\0' ) && ('b' == 'b')

当条件为真时,循环体中的语句将再次执行。现在strg1点寻址2002strg2点寻址3002。这将结束第二次迭代。

第三次迭代

在第三次迭代中strg1strg2分别指向字符'c''z'的地址。因此

*strg1返回'c'
*strg2返回'z'

再次测试条件时:

( 'c' != '\0' && 'z' != '\0' ) && ('c' == 'z')

while 条件变为 false,控件脱离 while 循环。检查 while 循环之后的 if 条件。

if( *strg1 == *strg2)
{
    
    
   return 0;  // strings are identical
}

因为

*strg1返回'c'
*strg2返回'z'

因此条件'c' == 'z'为假。控制来到 else 块,并执行下面的语句。

return *strg1 - *strg2;

表达式*strg1 - *strg2计算字符的 ASCII 值之差。

*strg1 - *strg2
=> 'c' - 'z'
=> 99 - 122
=> -23

最后-23返回到调用函数。

下面的程序演示了我们新的字符串比较函数my_strcmp()

#include<stdio.h>
int my_strcmp(char *strg1, char *strg2);

int main()
{
    
    

    printf("strcmp(\"a\", \"a\") = %d\n", my_strcmp("a", "a") );
    printf("strcmp(\"a\", \"b\") = %d\n", my_strcmp("a", "b") );
    printf("strcmp(\"a\", \"c\") = %d\n", my_strcmp("a", "c") );
    printf("strcmp(\"z\", \"d\") = %d\n", my_strcmp("z", "d") );
    printf("strcmp(\"abc\", \"abe\") = %d\n", my_strcmp("abc", "abe") );
    printf("strcmp(\"apples\", \"apple\") = %d\n", my_strcmp("apples", "apple") );

    // signal to operating system program ran fine
    return 0;
}

int my_strcmp(char *strg1, char *strg2)
{
    
    

    while( ( *strg1 != '\0' && *strg2 != '\0' ) && *strg1 == *strg2 )
    {
    
    
        strg1++;
        strg2++;
    }

    if(*strg1 == *strg2)
    {
    
    
        return 0; // strings are identical
    }

    else
    {
    
    
        return *strg1 - *strg2;
    }
}

预期输出:

strcmp("a", "a") = 0
strcmp("a", "b") = -1
strcmp("a", "c") = -2
strcmp("z", "d") = 22
strcmp("abc", "abe") = -2
strcmp("apples", "apple") = 115

可以看到,my_strcmp()返回不匹配字符的 ASCII 值。作为作业,修改此功能,如果strg1大于strg2则返回1,如果strg1小于strg2则返回-1



C 语言中的strcpy()函数

原文:https://overiq.com/c-programming-101/the-strcpy-function-in-c/

最后更新于 2020 年 7 月 27 日


strcpy()函数的语法是:

语法: char* strcpy (char* destination, const char* source);

strcpy()功能用于复制字符串。它将source指向的字符串复制到destination中。该函数接受指向char或字符数组的类型指针的两个参数,并返回指向第一个字符串的指针,即destination。注意source前面有const修饰符,因为strcpy()功能不允许改变source字符串。

以下程序演示了strcpy()功能的作用。

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

int main()
{
    
    
    char ch_arr1[20];
    char ch_arr2[20];

    printf("Enter first string (ch_arr_1): ");
    gets(ch_arr1);

    printf("Enter second string(ch_arr_1): ");
    gets(ch_arr2);

    printf("\nCopying first string into second... \n\n");
    strcpy(ch_arr2, ch_arr1); // copy the contents of ch_arr1 to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    printf("\nCopying \"Greece\" string into ch_arr1 ... \n\n");
    strcpy(ch_arr1, "Greece"); // copy Greece to ch_arr1

    printf("\nCopying \"Slovenia\" string into ch_arr2 ... \n\n");
    strcpy(ch_arr2, "Slovenia"); // copy Slovenia to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter first string (ch_arr_1): Mexico
Enter second string(ch_arr_1): South Africa

Copying first string into second...

First string (ch_arr_1) = Mexico
Second string (ch_arr_2) = Mexico

Copying "Greece" string into ch_arr1 ...
Copying "Slovenia" string into ch_arr2 ...

First string (ch_arr_1) = Greece
Second string (ch_arr_2) = Slovenia

需要注意的是strcpy()功能不检查destination是否有足够的大小来存储源中存在的所有字符。程序有责任确保destination数组有足够的空间容纳源字符串的所有字符。

关于strcpy()需要注意的另一个要点是,您永远不应该将字符串作为第一个参数传递。例如:

char ch_arr[] = "string array";

strcpy("destination string", c_arr); // wrong

这里您试图将ch_arr的内容复制到“目标字符串”中,它是一个字符串。由于修改字符串文字会导致未定义的行为,以这种方式调用strcpy()可能会导致程序崩溃。

让我们创建自己版本的strcpy()函数。

char *my_strcpy(char *destination, char *source)
{
    
    
    char *start = destination;

    while(*source != '\0')
    {
    
    
        *destination = *source;
        destination++;
        source++;
    }

    *destination = '\0'; // add '\0' at the end
    return start;
}

工作原理:

my_strcpy()函数接受指向char(char*)的类型指针的两个参数,并返回指向第一个字符串的指针。

在第 18 行中,我们已经将destination的基址分配给了start,这是必要的,否则我们将会丢失字符串开头的地址。

在第 20 行,我们有 while 循环,while 循环将字符从source逐个复制到destination。当源指向空字符('\0')的地址时,复制停止。

此时,start 指向的字符串包含除空字符('\0')之外的所有源字符。第 13 行的语句将一个空字符('\0')附加到字符串中。

在第 14 行中,return语句返回调用函数的字符指针。

让我们重写之前的程序,加入my_strcpy()函数的定义。

#include<stdio.h>
char *my_strcpy(char *destination, char *source);

int main()
{
    
    
    char ch_arr1[20];
    char ch_arr2[20];

    printf("Enter first string (ch_arr_1): ");
    gets(ch_arr1);

    printf("Enter second string(ch_arr_1): ");
    gets(ch_arr2);

    printf("\nCopying first string into second... \n\n");
    my_strcpy(ch_arr2, ch_arr1); // copy the contents of ch_arr1 to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    printf("\nCopying \"Greece\" string into ch_arr1 ... \n");
    my_strcpy(ch_arr1, "Greece"); // copy Greece to ch_arr1

    printf("\nCopying \"Slovenia\" string into ch_arr2 ... \n\n");
    my_strcpy(ch_arr2, "Slovenia"); // copy Slovenia to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    // signal to operating system program ran fine
    return 0;
}

char *my_strcpy(char *destination, char *source)
{
    
    
    char *start = destination;

    while(*source != '\0')
    {
    
    
        *destination = *source;
        destination++;
        source++;
    }

    *destination = '\0';
    return start;
}

预期输出:

Enter first string (ch_arr_1): Mexico
Enter second string(ch_arr_1): South Africa

Copying first string into second...

First string (ch_arr_1) = Mexico
Second string (ch_arr_2) = Mexico

Copying "Greece" string into ch_arr1 ...
Copying "Slovenia" string into ch_arr2 ...

First string (ch_arr_1) = Greece
Second string (ch_arr_2) = Slovenia

strcpy()my_strcpy()的输出相同,说明我们的程序工作正常。



C 语言中的strcat()函数

原文:https://overiq.com/c-programming-101/the-strcat-function-in-c/

最后更新于 2020 年 7 月 27 日


strcat()函数的语法是:

语法: char* strcat (char* strg1, const char* strg2);

该函数用于连接两个字符串。该函数接受两个指向char(char*)的类型指针参数,因此您可以传递字符串或字符数组。删除第一个字符串中的空字符,然后在第一个字符串的末尾追加第二个字符串。它返回一个指向结果字符串(strg1)的指针。一般情况下strcat()的返回值会被丢弃。

以下是一些例子:

char strg1[40] = "Hello";

/*
returns a pointer (which is discarded) to the string literal
"Hello World" and now strg1 contains "Hello World"
*/
strcat(strg1, " World");

/* 
returns a pointer (which is discarded) to the string
to "Hello World :)" and now strg1 contains "Hello World :)"
*/
strcat(strg1, " :)");

您不应该将字符串作为第一个参数传递,因为如果您这样做了,那么strcat()函数将试图修改字符串,这是一种未定义的行为,可能会导致程序崩溃。

strcat("Yello", " World"); // wrong

strg1指向的数组的大小不足以容纳来自strg2的所有字符时,strcat()的行为是未定义的。程序员的责任是确保strg1指向的数组的大小足够长,可以容纳所有来自strg2的字符。

下面的程序演示了如何使用strcat()功能。

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

int main()
{
    
    
    char strg1[40];
    char strg2[40];

    printf("Enter first string: ");
    gets(strg1);

    printf("Enter second string: ");
    gets(strg2);

    printf("\nConcatenating first and second string .. \n\n");
    strcat(strg1, strg2);

    printf("First string: %s\n", strg1);
    printf("Second string: %s", strg2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter first string: top
Enter second string: pot

Concatenating first and second string ..

First string: toppot
Second string: pot

让我们创建自己版本的strcat()函数。

char *my_strcat(char *strg1, char *strg2)
{
    
    
    char *start = strg1;

    while(*strg1 != '\0')
    {
    
    
        strg1++;
    }

    while(*strg2 != '\0')
    {
    
    
        *strg1 = *strg2;
        strg1++;
        strg2++;
    }

    *strg1 = '\0';
    return start;
}

工作原理:

my_strcat()函数接受指向char(char*)的类型指针的两个参数,并返回指向第一个字符串即strg1的指针。

在第 3 行中,我们已经将指针strg1分配给了start,这一步是必要的,否则我们将失去对第一个字符串(strg1)开头的地址的跟踪。

第一个 while 循环的工作是将指针strg1移动到最后一个字符,即'\0'。以便第二个 while 循环可以在这个位置开始追加字符。

第二个 while 循环将第二个字符串中的字符逐个追加到第一个字符串中。因为在第一个 while 循环之后strg1指向第一个字符串的空字符,所以在第一次迭代中,语句:

*strg1 = *strg2;

将第二个字符串中的第一个字符追加到第一个字符串的末尾(即代替空字符'\0')。然后strg1++strg2++递增。这个过程一直重复,直到在第二个字符串中遇到空字符(strg2)。

此时,start 指向的字符串仍然缺少一个东西,空字符('\0')。该声明:

*strg1 = '\0';

在第一个字符串的末尾追加空字符。

最后,return语句将指向第一个字符串的指针返回给调用函数。



C 语言中的字符数组和字符指针

原文:https://overiq.com/c-programming-101/character-array-and-character-pointer-in-c/

最后更新于 2020 年 7 月 27 日


在本章中,我们将研究字符数组和字符指针之间的区别。考虑以下示例:

char arr[] = "Hello World"; // array version
char ptr* = "Hello World";  // pointer version

你能指出它们之间的相似之处或不同之处吗?

相似之处在于:

这两个变量的类型都是指向char(char*)的指针,因此您可以将它们中的任何一个传递给其形式参数接受字符数组或字符指针的函数。

以下是不同之处:

  1. arr12字符的数组。当编译器看到以下语句时:

    char arr[] = "Hello World";
    
    

    它分配12个连续字节的内存,并将第一个分配字节的地址与arr相关联。
    T3】

    另一方面,当编译器看到语句时。

    char ptr* = "Hello World";
    
    

    它为字符串文字"Hello World"分配12连续字节,为指针变量ptr分配4额外字节。并将字符串文字的地址分配给ptr。所以,在这种情况下,总共分配了16个字节。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 我们已经了解到数组的名称是一个常量指针。所以如果arr指向地址2000,在程序结束前它会一直指向地址2000,我们无法更改它的地址。这意味着字符串赋值对于定义为数组的字符串无效。

    arr = "Yellow World"; // Wrong
    
    

    相反,ptr是一个类型为char的指针变量,所以可以取任何其他地址。作为结果字符串,赋值对指针有效。

    ptr = "Yellow World"; // ok
    
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    上述赋值后,ptr指向存储在内存某处的"Yellow World"的地址。

    显然,问题出现了,那么我们如何给arr分配不同的字符串呢?

    我们可以通过使用gets()scanf()strcpy()或者通过逐个分配字符来为arr分配一个新字符串。

    gets(arr);
    scanf("%s", arr);
    strcpy(arr, "new string");
    arr[0] = 'R';
    arr[1] = 'e';
    arr[2] = 'd';
    arr[3] = ' ';
    arr[4] = 'D';
    arr[5] = 'r';
    arr[6] = 'a';
    arr[7] = 'g';
    arr[8] = 'o';
    arr[9] = 'n';
    
    
  3. 回想一下,修改字符串文字会导致未定义的行为,因此以下操作无效。

    char *ptr = "Hello";
    ptr[0] = 'Y'; or *ptr = 'Y';
    gets(name);
    scanf("%s", ptr);
    strcpy(ptr, "source");
    strcat(ptr, "second string");
    
    
  4. 使用未初始化的指针也可能导致未定义的行为。

    char *ptr;
    
    

    这里ptr是未初始化的一个包含垃圾的值。所以下面的操作是无效的。

    ptr[0] = 'H';
    gets(ptr);
    scanf("%s", ptr);
    strcpy(ptr, "source");
    strcat(ptr, "second string");
    
    

    只有当ptr指向一个有效的内存位置时,我们才能使用它。

    char str[10];
    char *p = str;
    
    

    现在上面提到的所有操作都有效。我们可以使用 ptr 的另一种方法是使用malloc()calloc()函数动态分配内存。

    char *ptr;
    ptr = (char*)malloc(10*sizeof(char)); // allocate memory to store 10 characters
    
    

    让我们通过创建动态的一维字符数组来结束这一章。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    
    
    int n, i;
    char *ptr;

    printf("Enter number of characters to store: ");
    scanf("%d", &n);

    ptr = (char*)malloc(n*sizeof(char));

    for(i=0; i < n; i++)
    {
    
    
        printf("Enter ptr[%d]: ", i);
        /* notice the space preceding %c is
          necessary to read all whitespace in the input buffer
        */
        scanf(" %c", ptr+i); 
    }

    printf("\nPrinting elements of 1-D array: \n\n");

    for(i = 0; i < n; i++)
    {
    
    
        printf("%c ", ptr[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter number of characters to store: 6
Enter ptr[0]: a
Enter ptr[1]: b
Enter ptr[2]: c
Enter ptr[3]: d
Enter ptr[4]: y
Enter ptr[5]: z

Printing elements of 1-D array:

a b c d y z



C 语言中的字符串数组

原文:https://overiq.com/c-programming-101/array-of-strings-in-c/

最后更新于 2020 年 7 月 27 日


什么是字符串数组?

字符串是一维字符数组,因此字符串数组是二维字符数组。就像我们可以创建intfloat等的二维数组一样;我们还可以创建一个二维字符数组或字符串数组。下面是我们如何声明一个二维字符数组。

char ch_arr[3][10] = {
    
    
                         {
    
    's', 'p', 'i', 'k', 'e', '\0'},
                         {
    
    't', 'o', 'm','\0'},
                         {
    
    'j', 'e', 'r', 'r', 'y','\0'}
                     };

用空字符结束每个一维数组很重要,否则,它将只是一个字符数组。我们不能把它们当作琴弦。

以这种方式声明字符串数组相当繁琐,这就是为什么 C 语言提供了一种替代语法来实现同样的事情。上述初始化相当于:

char ch_arr[3][10] = {
    
    
                         "spike",
                         "tom",
                         "jerry"
                     };

数组的第一个下标,即3表示数组中的字符串数量,第二个下标表示字符串的最大长度。回想一下,在 C 语言中,每个字符占用1字节的数据,所以当编译器看到上面的语句时,它会分配30字节(3*10)的内存。

我们已经知道数组的名称是指向数组第 0 个元素的指针。你能猜出ch_arr的类型吗?

ch_arr是指向一组10字符或int(*)[10]的指针。

因此,如果ch_arr指向地址1000,那么ch_arr + 1将指向地址1010

由此,我们可以得出结论:

ch_arr + 0指向第 0 个字符串或第 0 个一维数组。
ch_arr + 1指向第一个字符串或第一个一维数组。
ch_arr + 2指向第二个字符串或第二个一维数组。

一般来说,ch_arr + i指向第 ith 个字符串或者第 ith 个一维数组。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们知道,当我们取消引用指向数组的指针时,我们得到了数组的基址。因此,在解引用ch_arr + i时,我们得到第 0 个一维数组的基址。

由此我们可以得出结论:

*(ch_arr + 0) + 0指向第 0 个一维数组的第 0 个字符(即s )
*(ch_arr + 0) + 1指向第 0 个一维数组的第 1 个字符(即p )
*(ch_arr + 1) + 2指向第 1 个一维数组的第 2 个字符(即m

总的来说,我们可以说:*(ch_arr + i) + j指向一维数组的 jth 字符。

注意*(ch_arr + i) + j的基类型是指向char(char*)的指针,ch_arr + i的基类型是 10 个字符的数组或int(*)[10]

要获取一维数组第十个位置的元素,只需取消引用整个表达式*(ch_arr + i) + j

*(*(ch_arr + i) + j)

我们在“指针和二维数组”一章中了解到,在二维数组中,指针符号相当于下标符号。所以上面的表达式可以写成如下:

ch_arr[i][j]

下面的程序演示了如何打印字符串数组。

#include<stdio.h>

int main()
{
    
    
    int i;

    char ch_arr[3][10] = {
    
    
                             "spike",
                             "tom",
                             "jerry"
                         };

    printf("1st way \n\n");

    for(i = 0; i < 3; i++)
    {
    
    
        printf("string = %s \t address = %u\n", ch_arr + i, ch_arr + i);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

string = spike address = 2686736
string = tom address = 2686746
string = jerry address = 2686756

对字符串数组的一些无效操作

char ch_arr[3][10] = {
    
    
                         {
    
    's', 'p', 'i', 'k', 'e', '\0'},
                         {
    
    't', 'o', 'm','\0'},
                         {
    
    'j', 'e', 'r', 'r', 'y','\0'}
                     };

它分配30字节的内存。即使我们在声明时没有初始化数组的元素,编译器也会做同样的事情。

我们已经知道数组的名称是一个常量指针,所以下面的操作是无效的。

ch_arr[0] = "tyke";   // invalid
ch_arr[1] = "dragon"; // invalid

在这里,我们试图将一个字符串(一个指针)赋给一个常量指针,这显然是不可能的。

要为ch_arr分配新字符串,请使用以下方法。

strcpy(ch_arr[0], "type"); // valid
scanf(ch_arr[0], "type");  // valid

让我们通过创建另一个简单的程序来结束这一章。

这个程序要求用户输入用户名。如果输入的用户名是主列表中的一个名称,则允许用户计算一个数字的阶乘。否则,将显示一条错误消息。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    
    
    int i, found = 0, n;

    char master_list[5][20] = {
    
    
                                  "admin",
                                  "tom",
                                  "bob",
                                  "tim",
                                  "jim"
                              }, name[10];

    printf("Enter username: ");
    gets(name);

    for(i = 0; i < 5; i++)
    {
    
    
        if(strcmp(name, master_list[i]) == 0 )
        {
    
    
            found = 1;
            break;
        }
    }

    if(found==1)
    {
    
    
        printf("\nWelcome %s !\n", name);
        printf("\nEnter a number to calculate the factorial: ");
        scanf("%d", &n);
        printf("Factorial of %d is %d", n, factorial(n));
    }

    else
    {
    
    
        printf("Error: You are not allowed to run this program.", name);
    }

    // signal to operating system program ran fine
    return 0;
}

int factorial(int n)
{
    
    
    if(n == 0)
    {
    
    
        return 1;
    }

    else
    {
    
    
        return n * factorial(n-1);
    }
}

**预期输出:**第一次运行:

Enter username: admin

Welcome admin !

Enter a number to calculate the factorial: 4
Factorial of 4 is 24

第二次运行:

Enter username: jack
Error: You are not allowed to run this program.

工作原理:

程序要求用户输入姓名。输入名称后,它会使用strcmp()功能将输入的名称与master_list数组中的名称进行比较。如果找到匹配,则strcmp()返回0,如果条件strcmp(name, master_list[i]) == 0变为真。找到的变量被赋值为1,这意味着允许用户访问程序。程序要求用户输入一个数字,并显示一个数字的阶乘。

如果输入的名称不是master_list数组中的一个名称,则程序通过显示错误信息退出。



C 语言中指向字符串的指针数组

原文:https://overiq.com/c-programming-101/array-of-pointers-to-strings-in-c/

最后更新于 2020 年 7 月 27 日


在上一章中,我们已经学习了如何使用字符串数组或二维字符数组。它可能会在您需要存储多个字符串时出现,那么字符串数组就是您要走的路,不幸的是,事实并非如此。考虑下面的例子。

char sports[5][15] = {
    
    
                         "golf",
                         "hockey",
                         "football",
                         "cricket",
                         "shooting"
                     };

sports数组存储在存储器中,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如您所看到的,并不是所有的字符串都足够长来填充数组的所有行,这就是编译器用空字符('\0')填充这些空格(使用浅灰色突出显示)的原因。运动阵列的总大小为75字节,但仅使用了34字节,浪费了41字节。41字节可能不会出现很多,但是在一个大型程序中,相当多的字节会被浪费掉。我们需要的是一个交错数组:一个二维数组,它的行可以有不同的长度。C 语言不提供交错数组,但是我们可以使用指向字符串的指针数组来模拟它们。

指向字符串的指针数组

指向字符串的指针数组是一个字符指针数组,其中每个指针指向字符串的第一个字符或字符串的基址。让我们看看如何声明和初始化指向字符串的指针数组。

char *sports[5] = {
    
    
                      "golf",
                      "hockey",
                      "football",
                      "cricket",
                      "shooting"
                  };

这里sports是一个指向字符串的指针数组。如果数组的初始化是在声明时完成的,那么我们可以省略数组的大小。所以上面的说法也可以写成:

char *sports[] = {
    
    
                     "golf",
                     "hockey",
                     "football",
                     "cricket",
                     "shooting"
                 };

需要注意的是,sports 数组的每个元素都是字符串,由于字符串指向第一个字符的基址,因此 sports 数组每个元素的基类型都是指向char(char*)的指针。

第 0 个元素即arr[0]指向字符串"golf"的基址。同样,第一个元素arr[1]指向字符串"hockey"的基址,以此类推。

以下是指向字符串的指针数组是如何存储在内存中的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

34 + 20 = 54

在这种情况下,所有字符串文字占用34字节,20字节由指针数组(即 sports)占用。因此,仅仅通过创建一个指向字符串的指针数组,而不是字符的二维数组,我们就节省了21字节(75-54=21)的内存。

需要强调的是,在指向字符串的指针数组中,不能保证所有字符串都存储在连续的内存位置。虽然特定字符串文字的字符总是存储在连续的内存位置。

下面的程序演示了如何访问字符串指针数组中的字符串文字,并在此过程中打印每个字符串文字的地址。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    
    
    int i = 1, *ip = &i;

    char *sports[] = {
    
    
                         "golf",
                         "hockey",
                         "football",
                         "cricket",
                         "shooting"
                     };

    for(i = 0; i < 5; i++)
    {
    
    
        printf("String = %10s", sports[i] );
        printf("\tAddress of string literal = %u\n", sports[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

String = golf Address of string literal = 4206592
String = hockey Address of string literal = 4206597
String = football Address of string literal = 4206604
String = cricket Address of string literal = 4206613
String = shooting Address of string literal = 4206621

在上一章中,我们了解到不能使用赋值运算符(=)将新字符串赋给二维字符数组。

char games[3][10] = {
    
    
                        "roadrash",
                        "nfs",
                        "angrybirds"
                    };

games[0] = "hitman";   // wrong

但是同样的事情也可以用指向字符串的指针数组来完成。

char *games[3] = {
    
    
                     "roadrash",
                     "nfs",
                     "angrybirds"
                 };

games[0] = "hitman";   // ok

由于games数组的每个元素都是指向char(char*)的指针,因此它可以指向分配给它的任何字符串。

对字符串指针数组的一些无效操作

让我们讨论一些不能在指向字符串的指针数组中直接执行的操作。考虑以下示例:

char *top_games[5];

当编译器看到上面的语句时,它会保留20字节的内存(4*5)来存储类型为char5指针,但不会为字符串分配任何内存。此时,top_games数组的所有元素都包含垃圾值,并且可能指向内存中的任何位置。这意味着以下操作无效。

scanf("%s", top_games[0]);             // invalid
strcpy(top_games[0], "mario");         // invalid
gets(top_games[0]);                    // invalid
strcat(top_games[0], "needforspeed");  // invalid



C 语言中的sprintf()函数

原文:https://overiq.com/c-programming-101/the-sprintf-function-in-c/

最后更新于 2020 年 7 月 27 日


sprintf()的工作方式与printf()类似,但它不是向控制台发送输出,而是返回格式化的字符串。

语法: int sprintf(char *str, const char *control_string, [ arg_1, arg_2, ... ]);

sprintf()函数的第一个参数是指向目标字符串的指针。其余参数与printf()函数相同。

该函数将数据写入str指向的字符串,并返回写入str的字符数,不包括空字符。返回值通常会被丢弃。如果操作过程中出现错误,它将返回-1

下面的程序演示了如何使用sprintf()功能。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    
    

    int sal;
    char name[30], designation[30], info[60];

    printf("Enter your name: ");
    gets(name);

    printf("Enter your designation: ");
    gets(designation);

    printf("Enter your salary: ");
    scanf("%d", &sal);

    sprintf(info, "Welcome %s !\nName: %s \nDesignation: %s\nSalary: %d",
        name, name, designation, sal);

    printf("\n%s", info);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter your name: Bob
Enter your designation: Developer
Enter your salary: 230000

Welcome Bob!
Name: Bob
Designation: Developer
Salary: 230000

sprintf()函数的另一个重要用途是将整数值和浮点值转换为字符串。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    
    
    char s1[20];
    char s2[20];

    int x = 100;
    float y = 300;

    sprintf(s1, "%d", x);
    sprintf(s2, "%f", y);

    printf("s1 = %s\n", s1);
    printf("s2 = %s\n", s2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

s1 = 100
s2 = 300.000000



C 语言中的sscanf()函数

原文:https://overiq.com/c-programming-101/the-sscanf-function-in-c/

最后更新于 2020 年 7 月 27 日


sscanf()功能允许我们从字符串而不是标准输入或键盘中读取格式化数据。它的语法如下:

语法: int sscanf(const char *str, const char * control_string [ arg_1, arg_2, ... ]);

第一个参数是指向我们要从中读取数据的字符串的指针。sscanf()其余论点与scanf()相同。如果遇到错误,它将返回从字符串中读取的项目数和-1

以下程序演示了sscanf()是如何工作的:

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

int main()
{
    
    
    char *str = "Tom Manager 28";
    char name[10], designation[10];
    int age, ret;

    ret = sscanf(str, "%s %s %d", name, designation, &age);

    printf("Name: %s\n", name);
    printf("Designation: %s\n", designation);
    printf("Age: %d\n", age);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Name: Tom
Designation: Manager
Age: 28

工作原理:

在第 6 行,我们已经声明并初始化了一个类型为char的变量str

在第 7 行,我们已经声明了两个字符数组namedesignation,大小为10字符。

在第 8 行,我们已经声明了类型为int的变量age

第 10 行调用sscanf()函数,从str指向的字符串中读取数据。请注意,字符串"Tom Manager 28"包含由空格分隔的三条信息名称、名称和年龄。要读取所有三个项目,我们需要向scanf()函数提供三个适当类型的变量。然后变量ret被分配由sscanf()功能读取的项目数。在这种情况下,我们从字符串str中读取三个项目,因此3将被分配给ret

我们没有义务阅读字符串中的所有条目,如果我们愿意,我们也可以从中阅读一到两个条目。

ret = sscanf(str, "%s %s", name, designation);

这里我们只是阅读和名称和名称,这就是为什么只有两个变量提供给sscanf()

最后用printf()功能显示namedesignationageret



结构和联合

C 语言中的结构基础

原文:https://overiq.com/c-programming-101/structure-basics-in-c/

最后更新于 2020 年 7 月 27 日


C 语言中的结构用于创建新的数据类型。那么为什么我们需要创建新的数据类型呢?考虑以下示例:

假设我们正在创建一个存储学生记录的程序。一个学生有许多属性,如姓名、学号、分数、出勤率等。有些项目是字符串,有些是数字。这是解决这个问题的一种方法。

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

int main()
{
    
    
    char name[20];
    int roll_no, i;
    float marks[5];

    printf("Enter name: ");
    scanf("%s", name);

    printf("Enter roll no: ");
    scanf("%d", &roll_no);

    printf("\n");

    for(i = 0; i < 5; i++)
    {
    
    
        printf("Enter marks for %d: subject: ", i+1);
        scanf("%f", &marks[i]);
    }

    printf("\nYou entered: \n\n");

    printf("Name: %s\n", name);
    printf("roll no: %d\n", roll_no);

    printf("\n");

    for(i = 0; i < 5; i++)
    {
    
    
        printf("Marks in %d subject %f: l\n", i+1, marks[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

毫无疑问,使用这种方法,我们将能够存储学生的姓名、学号和分数。但问题是这种方法的可扩展性不是很好。如果我们想存储更多的学生,那么程序就变得很难处理。这种方法最大的缺点是,它模糊了我们正在与一个单一的实体——学生打交道的事实。

利用结构,我们可以很容易地解决这类问题。该结构允许我们将不同类型的相关数据组合在一个名称下。每个数据元素(或属性)都被称为成员。

定义结构

语法:

struct tagname
{
    
    
    data_type member1;
    data_type member2;
    ...
    ...
    data_type memberN;
};

这里struct是一个关键字,告诉 C 编译器正在定义一个结构。member1member2……memberN是结构的成员或者只是结构成员,必须在花括号({})内声明。每个成员声明都以分号(;)结束。标记名是结构的名称,用于声明这种结构类型的变量。需要注意的一点是,结构定义必须始终以右大括号后面的分号(;)结束。

如上所述,除了内置数据类型之外,该结构还提供了一种数据类型。从结构类型中声明的所有变量都将采用该模板的形式。

定义一个新的结构不会保留任何空间和内存,只有当我们声明这种结构类型的变量时,才会保留内存。还有一点很重要,结构定义里面的成员是依附于结构变量的,没有结构变量他们就没有任何存在。结构内部的成员名称必须不同,但两个不同结构的成员名称可以相同。

让我们定义一个叫做学生的简单结构。

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

这里我们定义了一个名为student的结构,它有三个结构成员nameroll_nomarks。您可以在全局和本地定义结构。如果结构是全局的,那么它必须放在所有函数之上,这样任何函数都可以使用它。另一方面,如果在函数内部定义了一个结构,那么只有该函数可以使用该结构。

创建结构变量

除非我们声明结构变量,否则我们不能以任何方式使用结构定义。

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

有两种方法可以声明结构变量:

  1. 有了结构定义
  2. 使用标记名

让我们从第一个开始。

有了结构定义

struct student
{
    
    
char name[20];
int roll_no;
float marks;
} student1, student2;

这里student1student2struct student类型的变量。如果在定义结构模板时声明了结构变量,则tagname是可选的。这意味着我们也可以将上述结构声明为:

struct
{
    
    
    char name[20];
    int roll_no;
    float marks;
} student1, student2;

以这种方式定义结构有几个限制:

  1. 由于这个结构没有与之相关的名称,我们不能在程序的任何其他地方创建这种结构类型的结构变量。如果您有必要声明这种结构类型的变量,那么您必须再次编写相同的模板。
  2. 我们不能将这些结构变量发送给其他函数。

由于上述限制,这种方法没有得到广泛应用。

使用标记名

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

要使用标记名声明结构变量,请使用以下语法:

语法: struct tagname variable_name;

其中variable_name必须是有效的标识符。

下面是我们如何创建struct student类型的结构变量。

struct student student1;

我们还可以通过用逗号(,)符号分隔来声明多个结构变量。

struct student student1, student2, student3;

当一个变量被声明时,编译器只在内存中保留空间。理解一个结构的成员按照它们被定义的顺序存储在内存中是很重要的。在这种情况下,学生类型的每个结构变量有 3 个成员,即:nameroll_no、标记。因此,编译器将分配足够的内存来容纳该结构的所有成员。所以这里每个结构变量占用28字节(20+4+4)的内存。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

**注:**在此图中,我们假设结构的成员之间没有间隙。正如您将在本章后面看到的,结构的成员通常会在它们之间留下一些间隙。

初始化结构变量

为了初始化结构变量,我们使用与初始化数组相同的语法。

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
} student1 = {
    
    "Jim", 14, 89};

struct student student2 = {
    
    "Tim", 10, 82};

这里student1的成员值将有name"Jim"roll_no14marks89。同样的,student2的成员值为name"Tim"10roll_no82marks

成员的值必须按照结构模板中定义的相同顺序和相同类型放置。

要理解的另一件重要的事情是,我们不允许在定义结构时初始化成员。

struct student
{
    
    
    char name[20] = "Phil";   // invalid
    int roll_no = 10;         // invalid
    float marks = 3.14;       // invalid
};

定义结构只会创建一个模板,在创建结构变量之前不会分配内存。因此在这一点上不存在称为nameroll_nomarks的变量,那么我们如何将数据存储在一个不存在的变量中呢?我们不能。

如果初始值设定项的数量少于成员的数量,那么剩余的成员被赋予一个值0。例如:

struct student student1 = {
    
    "Jon"};

与…相同

struct student student1 = {
    
    "Jon", 0, 0.0};

结构上的操作

创建结构定义和结构变量后。显然,下一个逻辑步骤是学习如何访问结构的成员。

点(.)运算符或成员运算符用于使用结构变量访问结构的成员。以下是语法:

语法: structure_variable.member_name;

我们可以通过编写结构变量,后跟点(.)运算符,后跟成员名称来引用结构的成员。例如:

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

struct student student1 = {
    
    "Jon", 44, 96};

要访问student1的名称,请使用student1.name,同样,要访问roll_nomarks,请分别使用student1.roll_nostudent1.marks。例如,以下语句将显示student_1成员的值。

printf("Name: %s", student_1.name);
printf("Name: %d", student_2.roll_no);
printf("Name: %f", student_1.marks);

我们可以像使用其他普通变量一样使用student1.namestudent1.roll_nostudent1.marks。它们可以被读取、显示、赋值、在表达式中使用、作为参数传递给函数等。

让我们尝试更改结构成员的值。

student_1.roll_no = 10; // change roll no of student_1 from 44 to 10
student_1.marks++;      // increment marks of student_1 by 1

回想一下运算符优先级和结合性一章,点(.)运算符的优先级高于++运算符和赋值运算符(=)。所以在上面的表达式中,第一个点(.)运算符应用于后面跟有++运算符的表达式中。

看看下面的陈述。

scanf("%s", student_1.name);

这里structure studentname成员是一个数组,数组名是指向数组第 0 个元素的常量指针。所以我们不需要在student_1.name之前加上&运算符。另一方面在声明中:

scanf("%d", &student_1.roll_no);

要求在student_2.roll_no前加&运算符,因为roll_no是变量名,不是指针。另一点值得注意的是,在上面的表达式中,&运算符之前应用了点(.)运算符。

我们还可以将一个结构变量赋给另一个相同类型的结构变量。

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

struct student student1 = {
    
    "Jon", 44, 96}, student2;

student2 = student1;

本声明将student1.name复制成student2.namestudent1.roll_no复制成student2.roll_no等等。

需要注意的是,我们不能对结构变量使用算术、关系和按位运算符。

student1 + student2;  // invalid
student1 == student2; // invalid
student1 & student2;  // invalid

下面的程序演示了我们如何定义一个结构和读取结构成员的值。

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

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

int main()
{
    
    
    struct student student_1 = {
    
    "Jim", 10, 34.5}, student_2, student_3;

    printf("Details of student 1\n\n");

    printf("Name: %s\n", student_1.name);
    printf("Roll no: %d\n", student_1.roll_no);
    printf("Marks: %.2f\n", student_1.marks);

    printf("\n");

    printf("Enter name of student2: ");
    scanf("%s", student_2.name);

    printf("Enter roll no of student2: ");
    scanf("%d", &student_2.roll_no);

    printf("Enter marks of student2: ");
    scanf("%f", &student_2.marks);

    printf("\nDetails of student 2\n\n");

    printf("Name: %s\n", student_2.name);
    printf("Roll no: %d\n", student_2.roll_no);
    printf("Marks: %.2f\n", student_2.marks);
    strcpy(student_3.name, "King");
    student_3.roll_no = ++student_2.roll_no;
    student_3.marks = student_2.marks + 10;

    printf("\nDetails of student 3\n\n");

    printf("Name: %s\n", student_3.name);
    printf("Roll no: %d\n", student_3.roll_no);
    printf("Marks: %.2f\n", student_3.marks);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Details of student 1

Name: Jim
Roll no: 10
Marks: 34.50

Enter name of student2: jack
Enter roll no of student2: 33
Enter marks of student2: 15.21

Details of student 2

Name: jack
Roll no: 33
Marks: 15.21

Details of student 3

Name: King
Roll no: 34
Marks: 25.21

工作原理:

这里我们已经初始化了三个类型为struct student的变量。第一个结构变量student_1在声明时初始化。第一个学生的详细信息会用printf()语句打印出来。然后程序要求用户输入nameroll_nomarks作为结构变量student_2。然后使用printf()语句打印student_2的详细信息。

我们知道student_3.name是一个数组,所以我们不能只给它分配一个字符串,这就是为什么在第 37 行中使用strcpy()函数给student_3.name分配一个字符串。

因为点(.)运算符的优先级大于++运算符。因此在表达式++student_2.roll_no中,点(.)运算符首先应用,然后student.roll_no的值递增,并最终分配给student_3.roll_no。同样在表达式student_2.marks + 10中,由于点(.)运算符的优先级大于+运算符,首先得到student_2的标记,然后将其值增加10并最终赋给student_3.marks。最后打印student_3的详细信息。

结构如何存储在内存中

结构的成员总是存储在连续的内存位置,但是每个成员占用的内存可能会有所不同。考虑以下程序:

#include<stdio.h>

struct book
{
    
    
    char title[5];
    int year;
    double price;
};

int main()
{
    
    
    struct book b1 = {
    
    "Book1", 1988, 4.51};

    printf("Address of title = %u\n", b1.title);
    printf("Address of year = %u\n", &b1.year);
    printf("Address of price = %u\n", &b1.price);

    printf("Size of b1 = %d\n", sizeof(b1));

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of title = 2686728
Address of year = 2686736
Address of price = 2686744
Size of b1 = 24

在结构上,书名占据5字节,年份占据4字节,价格占据8字节。所以结构变量的大小应该是17字节。但是,正如您在输出中看到的,变量b1的大小是24字节,而不是17字节。为什么会这样?

这是因为有些系统要求某些数据类型的地址是248的倍数。例如,有些机器只在偶数地址存储整数,unsigned long intdouble存储在4的倍数等地址。在我们的例子中,名称成员的地址是2686728,因为它是5字节long,所以它占据了2686728 - 2686732的所有地址。

我运行这些示例程序的机器以4的倍数存储整数,这就是为什么2686732之后的三个连续字节(即268673326867342686735)没有使用的原因。这些未使用的字节称为。需要注意的是,这些孔不属于结构的任何成员,但它们确实会影响结构的整体尺寸。所以下一个成员year存储在2686736(是 4 的倍数)。它占用从26867362686739的地址4字节。同样,2686739 之后的四个字节未被使用,最终price成员存储在地址2686744(是8的倍数)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传



C 语言中的结构数组

原文:https://overiq.com/c-programming-101/array-of-structures-in-c/

最后更新于 2020 年 7 月 27 日


声明结构数组与声明基本类型数组是一样的。因为数组是同一类型元素的集合。在结构数组中,数组的每个元素都属于结构类型。

让我们举个例子:

struct car
{
    
    
    char make[20];
    char model[30]; 
    int year;
};

下面是我们如何声明structure car的数组。

struct car arr_car[10];

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里arr_car10元素的数组,其中每个元素都是struct car类型。我们可以使用arr_car来存储struct car类型的10结构变量。为了访问单个元素,我们将使用下标符号([]),为了访问每个元素的成员,我们将像往常一样使用点(.)运算符。

arr_stu[0] : points to the 0th element of the array.
arr_stu[1] : points to the 1st element of the array.

等等。同样的,

arr_stu[0].name : refers to the name member of the 0th element of the array.
arr_stu[0].roll_no : refers to the roll_no member of the 0th element of the array.
arr_stu[0].marks : refers to the marks member of the 0th element of the array.

回想一下[]数组下标和点(.)运算符的优先级是相同的,它们从左到右求值。因此,在上面的表达式中,首先应用数组下标([]),然后应用点(.)运算符。数组下标([])和点(.)运算符是相同的,它们从左到右计算。因此,在上面的表达式中,首先应用[]数组下标,然后应用点(.)运算符。

让我们重写上一章中作为结构介绍的程序。

#include<stdio.h>
#include<string.h>
#define MAX 2

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

int main()
{
    
    
    struct student arr_student[MAX];
    int i;

    for(i = 0; i < MAX; i++ )
    {
    
    
        printf("\nEnter details of student %d\n\n", i+1);

        printf("Enter name: ");
        scanf("%s", arr_student[i].name);

        printf("Enter roll no: ");
        scanf("%d", &arr_student[i].roll_no);

        printf("Enter marks: ");
        scanf("%f", &arr_student[i].marks);
    }

    printf("\n");

    printf("Name\tRoll no\tMarks\n");

    for(i = 0; i < MAX; i++ )
    {
    
    
        printf("%s\t%d\t%.2f\n",
        arr_student[i].name, arr_student[i].roll_no, arr_student[i].marks);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter details of student 1

Enter name: Jim
Enter roll no: 1
Enter marks: 44

Enter details of student 2

Enter name: Tim
Enter roll no: 2
Enter marks: 76

Name Roll no Marks
Jim 1 44.00
Tim 2 76.00

工作原理:

在第 5-10 行,我们已经声明了一个名为student的结构。

在第 14 行,我们已经声明了一个类型为struct student的结构数组,其大小由符号常量MAX控制。如果你想增加/减少数组的大小,只要改变符号常量的值,我们的程序就会适应新的大小。

在第 17-29 行,第一个 for 循环用于输入学生的详细信息。

在第 36-40 行,第二个 for 循环以表格形式打印学生的所有详细信息。

初始化结构数组

我们还可以使用与初始化数组相同的语法来初始化结构数组。让我们举个例子:

struct car
{
    
    
    char make[20];
    char model[30]; 
    int year;
};
struct car arr_car[2] = {
    
    
                            {
    
    "Audi", "TT", 2016},
                            {
    
    "Bentley", "Azure", 2002}
                        };



作为 C 语言中结构成员的数组

原文:https://overiq.com/c-programming-101/array-as-member-of-structure-in-c/

最后更新于 2020 年 7 月 27 日


从本章开始,我们已经在结构中使用数组作为成员。不过,让我们再讨论一次。例如:

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks;
};

上面定义的student结构有一个成员name,它是一个 20 个字符的数组。

让我们创建另一个名为 student 的结构来存储 5 个科目的名称、卷号和分数。

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks[5];
};

如果student_1是类型为struct student的变量,那么:

student_1.marks[0] -指第一科的标记
student_1.marks[1] -指第二科的标记

等等。同样,如果arr_student[10]是类型为struct student的数组,那么:

arr_student[0].marks[0] -指第一科第一名学生的成绩arr_student[1].marks[2] -指第三科第二名学生的成绩

等等。

以下程序要求用户在 2 个科目中输入姓名、卷号和分数,并计算每个学生的平均分数。

#include<stdio.h>
#include<string.h>
#define MAX 2
#define SUBJECTS 2

struct student
{
    
    
    char name[20];
    int roll_no;
    float marks[SUBJECTS];
};

int main()
{
    
    
    struct student arr_student[MAX];
    int i, j;
    float sum = 0;

    for(i = 0; i < MAX; i++ )
    {
    
    
        printf("\nEnter details of student %d\n\n", i+1);

        printf("Enter name: ");
        scanf("%s", arr_student[i].name);

        printf("Enter roll no: ");
        scanf("%d", &arr_student[i].roll_no);

        for(j = 0; j < SUBJECTS; j++)
        {
    
    
            printf("Enter marks: ");
            scanf("%f", &arr_student[i].marks[j]);
        }
    }

    printf("\n");

    printf("Name\tRoll no\tAverage\n\n");

    for(i = 0; i < MAX; i++ )
    {
    
    
        sum = 0;

        for(j = 0; j < SUBJECTS; j++)
        {
    
    
            sum += arr_student[i].marks[j];
        }
        printf("%s\t%d\t%.2f\n",
             arr_student[i].name, arr_student[i].roll_no, sum/SUBJECTS);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter details of student 1

Enter name: Rick
Enter roll no: 1
Enter marks: 34
Enter marks: 65

Enter details of student 2

Enter name: Tim
Enter roll no: 2
Enter marks: 35
Enter marks: 85

Name Roll no Average

Rick 1 49.50
Tim 2 60.00

工作原理:

在第 3 行和第 4 行,我们已经声明了两个符号常量MAXSUBJECTS,分别控制学生和科目的数量。

在第 6-11 行,我们已经声明了一个结构学生,它有三个成员,即nameroll_nomarks

在第 15 行,我们已经声明了一系列大小为MAX的结构arr_student

在第 16 行,我们已经声明了两个int变量ij来控制循环。

在第 17 行,我们已经声明了一个float变量sum,并将其初始化为0。这个变量将用于累积某个学生的分数。

在第 19-34 行,我们有一个 for 循环,要求用户输入学生的详细信息。在这个 for 循环中,我们有一个嵌套的 for 循环,它要求用户输入学生在不同科目中获得的分数。

在第 40-50 行,我们有另一个 for 循环,其工作是打印学生的详细信息。请注意,每次迭代后sum被重新初始化为0,这是必要的,否则我们将不会得到正确的答案。嵌套 for 循环用于在变量 sum 中累积特定学生的分数。最后,第 48 行的打印语句打印了学生的所有详细信息。



C 语言中的嵌套结构

原文:https://overiq.com/c-programming-101/nested-structures-in-c/

最后更新于 2020 年 7 月 27 日


一个结构可以嵌套在另一个结构中。换句话说,结构的成员可以是任何其他类型,包括结构。下面是创建嵌套结构的语法。

语法:

structure tagname_1
{
    
    
    member1;
    member2;
    member3;
    ...
    membern;

    structure tagname_2
    {
    
    
        member_1;
        member_2;
        member_3;
        ...
        member_n;
    }, var1

} var2;

**注意:**结构的嵌套可以扩展到任意级别。

为了访问内部结构的成员,我们写一个外部结构的变量名,后面跟一个点(.)运算符,后面跟内部结构的变量,后面跟一个点(.)运算符,然后跟我们要访问的成员的名称。

var2.var1.member_1 -指结构的member_1``tagname_2
var2.var1.member_2-指结构的member_2``tagname_2
以此类推。

让我们举个例子:

struct student
{
    
    
    struct person
    {
    
    
        char name[20];
        int age;
        char dob[10];
    } p ;

    int rollno;
    float marks;
} stu;

这里我们把结构人定义为结构学生的一员。下面是我们如何访问人员结构的成员。

stu.p.name -指人的名字
stu.p.age -指人的年龄
stu.p.dob -指人的出生日期

需要注意的是,结构人不是独立存在的。我们不能在程序的任何其他地方声明类型为struct person的结构变量。

而不是在另一个结构中定义该结构。我们可以在外部定义它,然后在我们想要使用它的结构内部声明它是变量。例如:

struct person
{
    
    
    char name[20];
    int age;
    char dob[10];
};

我们可以把这个结构作为一个更大结构的一部分。

struct student
{
    
    
    struct person info;
    int rollno;
    float marks;
}

这里第一个成员是类型struct person。如果我们使用这种创建嵌套结构的方法,那么在创建其类型的变量之前,您必须首先定义结构。因此,在将人员结构变量用作结构学生的成员之前,您必须首先定义人员结构。

使用这种方法的好处是,现在我们可以在程序的任何其他地方声明一个类型为struct person的变量。

现在允许结构本身嵌套。例如:

struct citizen
{
    
    
    char name[50];
    char address[100];
    int age;
    int ssn;
    struct citizen relative; // invalid
}

初始化嵌套结构

嵌套结构可以在声明时初始化。例如:

struct person
{
    
    
    char name[20];
    int age;
    char dob[10];
};

struct student
{
    
    
    struct person info;
    int rollno;
    float marks[10];
}

struct student student_1 = {
    
    
                               {
    
    "Adam", 25, 1990},
                               101,
                               90
                           };

下面的程序演示了我们如何使用嵌套结构。

#include<stdio.h>

struct person
{
    
    
    char name[20];
    int age;
    char dob[10];
};

struct student
{
    
    
    struct person info;
    int roll_no;
    float marks;
};

int main()
{
    
    
    struct student s1;

    printf("Details of student: \n\n");

    printf("Enter name: ");
    scanf("%s", s1.info.name);

    printf("Enter age: ");
    scanf("%d", &s1.info.age);

    printf("Enter dob: ");
    scanf("%s", s1.info.dob);

    printf("Enter roll no: ");
    scanf("%d", &s1.roll_no);

    printf("Enter marks: ");
    scanf("%f", &s1.marks);

    printf("\n*******************************\n\n");

    printf("Name: %s\n", s1.info.name);
    printf("Age: %d\n", s1.info.age);
    printf("DOB: %s\n", s1.info.dob);
    printf("Roll no: %d\n", s1.roll_no);
    printf("Marks: %.2f\n", s1.marks);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Details of student:

Enter name: Phil
Enter age: 27
Enter dob: 23/4/1990
Enter roll no: 78123
Enter marks: 92

*******************************

Name: Phil
Age: 27
DOB: 23/4/1990
Roll no: 78123
Marks: 92.00

工作原理:

在第 3-8 行,我们已经声明了一个名为person的结构。

在第 10-15 行,我们已经声明了另一个名为student的结构,它的一个成员是类型struct student(如上声明)。

在第 19 行,我们已经声明了类型为struct student的变量s1

接下来的五个scanf()语句(第 23-36 行)要求用户输入学生的详细信息,然后使用printf()(第 40-44 行)语句打印出来。



猜你喜欢

转载自blog.csdn.net/wizardforcel/article/details/143582748