嵌入式学习---在 Linux 下的 C 语言学习 Day9

Day9

指针(Pointer)

指针与二维数组

二维数组在内存中是按行存储的。

二维数组存储形态如下图所示:

c 复制代码
#include <stdio.h>

int main()
{
        short a[][3] = {1,2,3,-4,-5,-6};
    	 *((short*)((short(*)[3])(&a + 1) - 1) + 1);

        /*
         * a        --> short(*)[3] 和下面的 &a[0] 一样
         * &a       --> short(*)a[2][3]
         * a[0]     --> *(a + 0) short* 和下面 &a[0][0] 一样
         * &a[0]    --> short(*)[3]
         * &a[0][0] --> short*
         */

        short *p = (short*)a;
        //short *p = &a[2][3];

        for(int i = 0;i < 6;i++)
        {
                printf("%hd ",*(p + i));

                if(i == 2)
                {
                        printf("\n");
                }
        }
        printf("\n");

        for(int i = 0;i < 2;i++)
        {
                for(int j = 0;j < 3;j++)
                {
                        printf("%hd ",*(*(a + i) + j));
                }
                printf("\n");
        }


        return 0;
}

字符型指针

C 语言没有提供专门的字符串(String)类型,可以使用字符型指针(char*)表示一个字符串。所以字符型指针有两个用途:

  • 指向一个字符
  • 代表一个字符串(首字符指针可以代表整个字符串,因为首字符指针确定了,末尾字符又是'\0',整个字符串就确定了)
c 复制代码
// 字符型指针的常规用法
char c = 'a';
char* p = &c;

(*p)++;
printf("%c\n",*p); // b

// 字符型指针表示字符串
char s1[] = "abc";
printf("%c\n",*s1); // a
printf("%c\n",*(s1 + 2)); // c

char* p1 = s1;
printf("%s\n",p1); // abc
printf("%s\n",p1 + 1);// bc
printf("%c\n",p1[2]); // c

char* s2 = "abc";
s2 = "qzp";

typedef unsigned long size_t

strlen 的源码:

c 复制代码
size_t strlen(const char* s)
{
    size_t len = 0;
    
    while(s[len] != '\0') len++;
    
    return len;
}
c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
        char   s1[] = "abc"; // char s1[] = {'a','b','c','\0'};
        char*  s2 = "abc";
        char*  s3 = "fasdjaisdjiahfjsi,,.,-=\nsdddddddsadasd";
        /*
        int    s3 = 3;
        double s4 = 3.14;
        */

        printf("%lu\n",sizeof(s1));
        printf("%lu\n",sizeof(s2));
        printf("%lu\n",sizeof(s3));

        printf("%lu\n",strlen(s1));
        printf("%lu\n",strlen(s2));
        printf("%lu\n",strlen(s3));


        return 0;
}

一个 char* 可以表示一个字符串,多个 char* 可以表示多个字符串,即使用字符型指针数组(char* 数组)可以表示多个字符串

野指针:指向不确定位置的指针

c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
        char* s1; // 野指针

        // ok
        s1++;
        s1 -= 3;

        // 访问野指针指向的数据,会发生不可预知的结果
        (*s1)++;
        (*s1) -= 3;


        char ss[100];
        s1 = ss;

        // s1 = "qzp"; // ok
        strcpy(s1,"qzp");

        char* s2 = NULL;
        printf("%c\n",*s2);

        return 0;
}

结构体(Struct)

若干个相同或不同类型数据数据的集合

数据成员(成员变量):Member Variables,构成结构体的数据

结构体是一种自定义数据类型,必须先声明,再使用

声明结构体类型的语法规则:

c 复制代码
struct student
{
    // 定义各个数据成员(成员变量),也称为字段(Field)
    int sno;
    int height;
    char name[10];
    char sex;		// 0 - 女 1 - 男 2 - 未知 3 - 保密
    float sight;
    char* addr;    // 家庭住址
    // ......
};

typedef struct
{
    int isbn;
    char name[50];
    float price;
} book;

通过使用已声明的结构体类型定义变量或常量

c 复制代码
struct student s1;				 // 定义 student 类型的变量 s1,未初始化
const struct student s2 = {1001,180,"张三",1,4.8,"广东深圳"};  // 定义 student 类型的常量 s2,必须初始化

struct student s3 = {1002,"张四",0,4.5,"北京"};

// 结构体指定初始化,(不是 C 语言标准语法,只有 gcc 支持,属于 gcc 编译器扩展的语法特性)
struct student s4 = {.name = "王五",.sno = 1003,.sight = 4.3};

book b1;
const book b2 = {1000,"计算机组成原理",35.5);
book b3 - {100002,"C语言",32.2};
                 
                 

中文编程语言:易语言等

不能直接访问整个结构体,只能访问结构体的数据成员:

c 复制代码
s1.sno = 1009;
strcpy(s1.name,"张三");
s1.addr = "湖北武汉";

//s2.sno = 1010; // error,因为 s2 是常量,也就是说它的所有数据成员都是只读的

printf("书号:%d\n书名:%s\n单价:%g\n",b3.isbn,b3.name,b3.price);

// 通过结构体指针访问它指向的结构体的数据成员要使用指向运算符(->)
struct student* p1 = &s1; // 定义一个 student 类型的指针变量 p1,初始值为 s1 的指针
p1 -> sno = 1009;
strcpy(p1->name,"张三");
p1->addr = "湖北武汉";

book* p2 = &b3;
printf("书号:%d\n书名:%s\n单价:%g\n",p2->isbn,p2->ame,p2->price);

举例:

c 复制代码
#include <stdio.h>
#include <string.h>

typedef struct
{
        int isbn;
        char name[50];
        float price;

} book;


int main()
{
        book b1;

        printf("请依次输入图书的各项信息:\n");
        printf("书号:");
        scanf("%d",&b1.isbn);
        printf("书名:");
        scanf("%s",b1.name);
        printf("单价:");
        scanf("%f",&b1.price);

        b1.price += 5;

        printf("\n图书信息如下:\n");
        printf("书号:%d\n书名:%s\n单价:%g\n",b1.isbn,b1.name,b1.price);

        book* p1 = &b1;
        strcat(p1->name,"(牛逼)");
        p1->price += 5;

        printf("书号:%d\n书名:%s\n单价:%g\n",p1->isbn,p1->name,p1->price);

        return 0;
}

位域成员

结构体嵌套:结构体的数据成员也是结构体类型

c 复制代码
struct wheel
{
    int id;
    char model[100];
    float price;
};

struct car
{
    int num;
    char model[100];
    struct wheel w;  // 结构体嵌套
    float price;
};

struct car c1 = {1001,"宝马X6",{2001,"米其林",200},180000};
c1.w.price += 10;

struct car* p1 = &c1;
p1->w.price += 10;

printf("汽车轮胎品牌:%s\n轮胎单价:%g\n",p1->w.model,c1.w.price);

如果需要表示多个结构体类型的数据,就用结构体数组

c 复制代码
typedef struct
{
    int isbn;
    char name[50];
    float price;
} book;

book bs[100] = {{1001,"计算机组成原理",35.5},{1002,"C语言",40.5}} // 结构体数组

bs[3].isbn = 1003;
strcpy(bs[3].name,"操作系统");
// 下面三种写法完全等效
bs[3].price = 45.5;
(*(bs + 3)).price = 45.5
(bs + 3)->price = 45.5

for(int i = 0;i < sizeof(bs) / sizeof(bs[0]);i++)
{
    printf("\n第 %d 本图书的信息如下:\n",i);  
    printf("书号:%d\n书名:%s\n单价:%g\n",bs[i].isbn,bs[i].name,bs[i].price);
}

在实际开发中,结构体往往很大(几十个或上百个数据成员都是常态),如果函数的返回值和形参需要使用结构体,强烈建议使用结构体指针,这样做效率更高,占用的内存空间也更小。

c 复制代码
struct student
{
    // 定义各个数据成员(成员变量),也称为字段(Field)
    int sno;
    int height;
    char name[10];
    char sex;		// 0 - 女 1 - 男 2 - 未知 3 - 保密
    float sight;
    char* addr;    // 家庭住址
    // ......
};

void set_stu(struct student* s)
{
    s->sight++;
    printf("%s\n",s->name);
}

void show_stu(const struct student* s)
{
    printf("%d\n",s->sno);
    printf("%s\n",s->name);
}

结构体的数据成员按照定义顺序连续存储,先定义的地址小,后定义的地址大,但它们不一定相邻,因为受到内存对齐机制的影响,所以结构体总大小会大于或等于它的所有数据成员的宽度之和

结构体大小计算规则:

  • 每个数据成员的相对地址必须是其类型宽度和内存对齐系数二者中较小值的整数倍
  • 结构体总大小必须为所有数据成员中的最大宽度和内存对齐系数二者中较小值的整数倍
  • 数组类型的数据成员就将它看作多个同类型数据成员处理就行,还是遵守上面两条规则
  • 结构体类型的数据成员的自身大小还是按照上面的三条规则计算,但是它的类型宽度应该用它内部最大数据成员的宽度计算

64 位系统上默认内存对齐系数是 8 字节,可以使用 #pragma pack 预处理命令修改内存对齐系数,内存对齐系数必须为 2^n,比如:1、2、4、8

c 复制代码
#include <stdio.h>

// #pragma pack(1)
struct person
{
        char sex;
        int num;
        short height;
        double sight;
        char state;
};

int main()
{
        printf("%lu\n",sizeof(struct person));

        return 0;
}


内存对齐机制虽然浪费了一些内存空间(中间会填充若干空闲字节),但换来了内存读写效率的提升