5、数据类型、运算符和表达式


一、数据类型

I、数据类型概述

在这里插入图片描述

  • 为什么要指明数据的类型?

    • 更好进行内存的管理,让编译器能确定分配多少内存
    • 规定数据所能进行的操作,限制非法运算(如:a%b , 若 a,b 为 float 类型则出错)
  • 数据类型的转换:

    • 显式类型转换: 常用于对函数的参数及返回值的操作,形式为:(类型名)表达式;类型名(表达式); static_cast<类型名> (表达式) // C++dynamic_cast<类型名> (表达式) // C++,比静态强制转换更安全,转换失败会返回 nullptr
    • 隐式类型转换:在不同类型数据进行混合运算时,系统自动进行的类型转换,一般按照从 低精度向高精度、 从有符号数向无符号数方向转换尤其注意此情况下可能出现的错误,最好先手动强转成相同类型
      在这里插入图片描述
  • 有符号数与无符号数:

    • 有符号数是最高位为符号位,0 代表正数,1 代表负数
    • 无符号数最高位不是符号位,而就是数的一部分,无符号数不可能是负数
  • 使用 auto 自动推断数据类型:

    • 若编译器支持 C++11 和更高版本,可不显式地指定变量的类型, 而使用关键字 auto
    • 使用 auto 时必须对变量进行初始化,这样编译器才能根据初始值来确定变量的类型
  • 使用 typedef 关键字可以将变量类型替换为其它好记的名称(加强程序的可移植性–>改一处即可):

    • 用法 1:为基本类型(int、float…)定义一个别名,eg:typedef 数据类型 新的数据类型名; eg:typedef int INT;
    • 用法 2:为自定义数据类型(数组、指针、结构体、共用体和枚举类型等构造类型)定义简洁的类型名称,eg:typedef char* PCHAR;
    • #define 的区别:typedef 仅限于数据类型,而不是能是表达式或具体的值, #define 发生在预处理阶段,typedef 发生在编译阶段
typedef char                s8;
typedef unsigned char       u8;
typedef short               s16;
typedef unsigned short      u16;
typedef int                 s32;
typedef unsigned int        u32;
typedef float               f32;
typedef double              d64;
typedef long long           s64;
typedef unsigned long long  u64;



// 不以 f 结尾的常量是 double 类型,以 f 结尾的常量(如 3.14f)是 float 类型
float b = 2.99f;
double c = 8.33;

// C 代码中,声明 struct 新对象时,必须要带上 struct,即形式为: struct 结构名 对象名,如:
struct tagPoint1
{
	int x;
	int y;
};
struct tagPoint1 p1;  // C 中需要加上 struct, 而在 C++ 中,则可以直接写:结构名 对象名,即:tagPoint1 p1;

// 经常多写一个 struct 太麻烦了,于是就发明了下面这种形式:
typedef struct tagPoint 
{
	int x;
	int y;
} TPoint;  // TPoint 为 struct tagPoint 的别名,其中前缀 T 代表结构体



TPoint p1; // 比原来的方式少写了一个 struct,比较省事

//  同样适用于枚举
typedef enum tagImageFormat
{
	IMGALG_NV12 = 0,
	IMGALG_BGR,
	IMGALG_NV21,
} EImageFormat;  // EImageFormat 为 enum tagImageFormat 的别名,其中前缀 T 代表枚举



/******* 重要:C/C++ 中结构体和枚举定义形式 **********/
typedef struct  // tagPoint 可省略不写
{
	int x;
	int y;
} TPoint;  // TPoint 为 struct tagPoint 的别名,其中前缀 T 代表结构体


typedef enum  // tagImageFormat 可不写
{
	IMGALG_NV12 = 0,
	IMGALG_BGR,
	IMGALG_NV21,
} EImageFormat;  // EImageFormat 为 enum tagImageFormat 的别名,其中前缀 T 代表枚举

II、构造类型之结构体

1、概述
  • 结构体是将不同类型的数据组合成一个有机整体以便于引用,解决了数组只能对同类型的数据进行处理的缺陷。
  • 在结构类型的声明时,系统不会为结构类型分配内存空间;只有在定义了结构体变量后,系统才会为之分配内存单元(普通结构体成员内存大小为 sizeof(type),而结构体指针成员内存大小始终为 4 个字节,对其赋值后会指向相应的内存地址)。
2、结构体变量的定义和初始化
  • 定义:

    • 先定义结构体类型,再定义结构体变量
    • 定义结构体类型的同时定义结构体变量
    • 直接定义结构体变量(无类型名)
    • 使用 typedef 对结构体变量进行定义(省略 struct)
      在这里插入图片描述
  • 初始化:普通结构体变量初始化、指针结构体变量初始化

    • 结构变量 = {结构成员1初始化值, ... , 结构成员n初始化值};
    • 结构数组初始化时按数组初始化原则所有元素组织在一对花括号中,元素间逗号分隔,最后一个元素/字段后面不需要加逗号
  • 代码示例:

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

// C++ 中的定义方式
struct 结构类型标识符(eg: Contact)         // 一种新的构造类型 Contact, 地位和 int、double 之类的相同         
{
	结构成员1; (eg: int id;)               // 结构成员定义形式:type  varName1;
	                                      // 成员变量的定义和普通变量的定义方式是一样的
 	结构成员2; (eg: char name[16];)
	┆
	结构成员n; (eg: char phone[16];)
};                                       // 请注意,别忘记这个小不点^_^!

// C++ 中定义并初始化
struct KNN_TModelOpenParams
{
    KD_U32 u32InNum;
    KD_U32 u32OutNum;
    KD_U32 s32IfEncrypt;
    std::string sEncryptKey; // 默认会创建一个空的字符串, 不加大括号初始化也可以
    std::string sModelPath;
    std::string sPriorboxPath;
    std::vector<std::string> vsInputNames;
    std::vector<std::string> vsOutputNames;
};


// C++ 中的聚合初始化:在所有参数均没有默认初始化参数的时候使用;此时会调用默认初始化(一般要指定构造函数或以列表的形式初始化,不使用默认的)
// 不能直接使用 KNN_TModelOpenParams tOpenparam = {0}, 这样只初始化了一个参数
KNN_TModelOpenParams tOpenparam1 = {};  // 默认构造函数初始化
KNN_TModelOpenParams tOpenparam1{};  // 默认构造函数初始化
KNN_TModelOpenParams tOpenparam1;  // 默认构造函数初始化
KNN_TModelOpenParams tOpenparam = {1, 1, 0, "kkk", "/model/det.bin", "ddd", {"input1", "input2"}, {"output"}};


// C 中的定义方式,一般包含在头文件中(经常多写一个 struct 太麻烦了,于是就发明了下面这种形式)
typedef struct 结构类型名(eg: tagContact) // struct 的别名;也可以不写      
{
	结构成员1; (eg: int id;)              // 结构成员定义形式:type  varName1;
							    	     // 成员变量的定义和普通变量的定义方式是一样的
 	结构成员2; (eg: char name[16];)
	┆
	结构成员n; (eg: char phone[16];)
} Contact;                                // 一种新的构造类型 Contact, 地位和 int、double 之类的相同



// 新类型的使用和基本类型差不多,它只是基本数据类型的组合
// 1、定义时进行初始化 
Contact c = {201501, "John", "18601011223"};
// 初始化结构体变量也要按照固定的顺序,但在 GNU C 中我们可以通过结构域名来指定初始化某个成员(可以不考虑顺序),
// 当结构体的成员很多时, 使用这种种初始化方式会更加方便
Contact d = {
        .name = "John",
        .phone = "18601011223",
        .id = 201501
};


// 2、定义一个变量,初始值设置为 0,用的时候再改变初值,注意此时字符类型的数组必须用 strcpy 来赋值
Contact c = {0}; 
c.id = 201501;
strcpy(c.name, "John");   
strcpy(c.phone, "18601011223");
memset(&c, 0, sizeof(Contact));  // 或者使用 string.h 中的 memset 进行置零,指针指向 NULL


// 3、定义数组并进行初始化
Contact cs[4] =
{
    {201501, "John",     "18601011223"},
    {201502, "Jennifer", "13810022334"},
    {201503, "AnXi",     "18600100100"},
    {201504, "Unnamed",  "18601011223"}   // 最后一个元素/字段后面不需要加逗号
};
cs[2].id = 201503
cs[2].name = "AnXi"                      // 格式化赋值:sprintf(cs[i].name, "AnXi_%d", i); 
cs[2].phone = "18600100100"


// 4、作为函数参数类型
void test(Contact c);   // 传值,使用 . 来获取结构体变量的值,缺点:使用了更多内存空间,花费较多 CPU 进行值拷贝
void test(Contact* p);  // 传地址,使用 -> 来获取结构体变量的值
void test(const Contact* who);  // 只是输入参数,加上 const 修饰


// 5、作为函数的返回值,直接返回一个 Contact 对象 
Contact find(int id);


// 6、C 语言位域:在结构体定义时,指定某个成员变量所占用的二进制位数(Bit),目的是尽量压缩存储空间
struct bs{
    unsigned m;
    unsigned n: 4;
    unsigned char ch: 6;
};
// : 后面的数字用来限定成员变量占用的位数。成员 m 没有限制,根据数据类型即可推算出它占用 4 个字节(Byte)的内存
// 成员 n、ch 被 : 后面的数字限制,不能再根据数据类型计算长度,它们分别占用 4、6 位(Bit)的内存
  • 指针结构体变量初始化
    • 首先,定义一个指针结构体变量,在堆上为其开辟内存(普通结构体成员已开辟好内存,而子指针结构体成员尚未指向具体的内存地址)
    • 其次,若此结构体中包含子指针结构体成员(Substruct*),那么此子指针结构体成员也要使用 calloc 进行初始化(若子指针结构体成员不进行初始化,则无法对其赋值
    • 然后,用的时候普通结构体成员(包括结构体)直接赋值即可,指针结构体成员直接赋地址即可
    • 最后,地址在堆上则内存在堆上(需要手动进行内存释放),地址在栈上则内存在栈上(系统自动进行内存释放)
    • Note: 指针结构体变量若暂时没有指向,可以先指向 NULLnullptr
// 指针结构体变量暂时没有指向
Contact* c = NULL;

// 指针结构体变量初始化
Contact* c = (Contact* c)calloc(1, sizeof(Contact));  
c.Substruct = (Substruct* c)calloc(1, sizeof(Substruct));

// 代码示例
#include <stdio.h>
#include <string.h>

typedef struct TagSubstruct {
    int ege;
    char phone[16];
} TSubstruct;

typedef struct TagContact {
    int id; // 0
    char *name;  // NULL
    TSubstruct *info;  // calloc 初始化后指向 NULL, 此字结构体也需要进行初始化(将其指向合法内存区域),否则无法对其进行赋值
} TContact;


int main(int argc, char *argv[]) {
    TContact *tC = (TContact *) calloc(1, sizeof(TContact));
    tC->id = 5;
    tC->name = "man";
    
    // 为子结构体变量开辟内存,指向合法的内存地址
    tC->info = (TSubstruct *) calloc(1, sizeof(TSubstruct));
    tC->info->ege = 18;
    strcpy(tC->info->phone, "13966668888");
    printf("id, name, ege, phone is: %d, %s, %d, %s", tC->id, tC->name, tC->info->ege, tC->info->phone);

    return 0;
}

// 输出如下:
id, name, ege, phone is: 5, man, 18, 13966668888
3、结构体成员的使用
  • 如果是普通变量,通过点运算符操作结构体成员strcpy(s1.name, "abc");
  • 如果是指针变量,通过 ->运算符操作结构体成员strcpy(p1->name, "test");
  • 结构体变量拷贝:如果结构体内部有指针指向堆内存,要注意内存泄露以及同一内存释放多次的问题
#pragma pack(4)

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


typedef struct tagTeacher {
    char *name;  // 指针类型,占用 8 个字节,在栈区,用于存地址数据(此地址需要事先开辟内存,用于存储数据)
    int age;     // int 类型,占用 4 个字节,在栈区,用于存整型数据
} Teacher;

void test() {
    Teacher teacher1;
    teacher1.name = (char *) calloc(1, sizeof(char) * 64); // 在堆区开辟空间,返回堆区内存首地址
    strcpy(teacher1.name, "aaa");  // "aaa" 占用四个字节
    teacher1.age = 20;

    Teacher teacher2;
    teacher2.name = (char *) calloc(1, sizeof(char) * 128);
    strcpy(teacher2.name, "bbbb"); // "bbbb" 占用五个字节
    teacher2.age = 30;

    printf("Name:%s Age: %d\n", teacher1.name, teacher1.age);
    printf("Name:%s Age: %d\n", teacher2.name, teacher2.age);
    printf("teacher1 size: %lu\n", sizeof(teacher1));
    printf("teacher2 size: %lu\n", sizeof(teacher2));


    // 如果结构体内部有指针指向堆内存,要注意内存泄露
    if (teacher1.name != NULL) {
        free(teacher1.name);
        teacher1.name = NULL;
    }

    teacher1 = teacher2;

    printf("----------------------------\n");
    printf("Name:%s Age: %d\n", teacher1.name, teacher1.age);
    printf("Name:%s Age: %d\n", teacher2.name, teacher2.age);

    // 释放堆内存(teacher1 和 teacher2 指向的是同一堆空间,只需要释放一个即可)
    if (teacher2.name != NULL) {
        free(teacher2.name);
        teacher2.name = NULL;
    }

}


int main() {
    test();
    return 0;
}


// 输出结果如下:
Name:aaa Age: 20
Name:bbbb Age: 30
teacher1 size: 12
teacher2 size: 12  (pragama pack(4); clion 中 char* 8 Byte; int 4 Byte)
----------------------------
Name:bbbb Age: 30
Name:bbbb Age: 30
4、结构体套结构体

一个结构类型的变量可以作为另外一个结构类型的成员

#include <stdio.h>

typedef struct tagPerson {
    char name[20];
    char sex;
} Person;

typedef struct tagStudent {
    int id;
    Person info;
} Student;

int main() {
    Student s[2] = {{1, "lily", 'F'},
                    {2, "yuri", 'M'}};

    for (int i = 0; i < 2; i++) {
        printf("id = %d, info.name = %s, info.sex = %c \n", s[i].id, s[i].info.name, s[i].info.sex);
    }

    return 0;
}

// 输出结果如下:
id = 1, info.name = lily, info.sex = F 
id = 2, info.name = yuri, info.sex = M 
5、指针指向结构体变量
a、指针变量指向普通结构体变量
#include <stdio.h>

// 结构体类型的定义
typedef struct tagStudent {
    char name[50];
    int age;
} Student;

int main() {
    Student s1 = {"lily", 18};

    // 如果是指针变量,通过 -> 操作结构体成员
    Student *p = &s1;
    printf("s1.name=%s, s1.age=%d\n", s1.name, s1.age);
    printf("p->name=%s, p->age=%d\n", p->name, p->age);
    printf("(*p).name=%s, (*p).age=%d\n", (*p).name, (*p).age);
    
    return 0;
}



// 输出结果如下:
s1.name=lily, s1.age=18
p->name=lily, p->age=18
(*p).name=lily, (*p).age=18
b、指针变量指向堆区结构体变量
#include<stdio.h>
#include <string.h>
#include <stdlib.h>

// 结构体类型的定义
typedef struct tagStudent {
    char name[50];
    int age;
} Student;

int main() {
    Student *p = (Student *) calloc(1, sizeof(Student));   // 指向堆区结构体变量

    // 如果是指针变量,通过->操作结构体成员
    // p 是指针结构体变量,p[0] 为普通的结构体变量
    strcpy(p->name, "test");  // strcpy(p[0].name, "test");
    p->age = 22;  // p[0].age = 22;

    printf("p->name = %s, p->age=%d\n", p->name, p->age);
    printf("(*p).name = %s, (*p).age=%d\n", (*p).name, (*p).age);
    printf("p address is %p, &p[0] address is %p\n", p, &p[0]);

    // 释放堆区分配的内存
    if (p != NULL) {
        free(p);
        p = NULL;
    }

    return 0;
}


// 输出结果如下:
p->name = test, p->age=22
(*p).name = test, (*p).age=22
p address is 0x7af010, &p[0] address is 0x7af010


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

typedef struct tagPerson {
    char name[64];
    int age;
} Person;

void test() {
    Person *p3 = (Person *) calloc(1, sizeof(Person) * 3);   // 指向堆区结构体数组

    // 堆上分配内存,p3 相当于结构体数组的首地址(它是一个指针变量)
    // p3[i] 则是一个普通的结构体变量
    for (int i = 0; i < 3; i++) {
        sprintf(p3[i].name, "Name_%d", i + 1);  // 格式化赋值
        p3[i].age = 20 + i;
    }
    for (int i = 0; i < 3; i++) {
        printf("Name is: %s, Age is: %d\n", p3[i].name, p3[i].age);
    }

    // 释放堆区分配的内存
    if (p3 != NULL) {
        free(p3);
        p3 = NULL;
    }
}


int main() {
    test();
    return 0;
}

// 输出结果如下:
Name is: Name_1, Age is: 20
Name is: Name_2, Age is: 21
Name is: Name_3, Age is: 22
c、指针变量指向堆区结构体变量(成员变量为一级指针变量)

内存释放时要注意: 先释放指针成员变量内存,然后再释放结构体指针变量内存

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

// 结构体类型的定义
typedef struct tagStudent {
    char *name; // 一级指针
    int age;
} Student;

int main() {
    Student *p = (Student *) calloc(1, sizeof(Student)); // 指向堆区结构体变量
    p->name = (char *) calloc(1, strlen("test") + 1);    // 为结构体成员指针变量 name 分配内存

    // 初始化
    strcpy(p->name, "test");
    p->age = 22;

    printf("p->name = %s, p->age=%d\n", p->name, p->age);
    printf("(*p).name = %s, (*p).age=%d\n", (*p).name, (*p).age);

    // 释放指针成员变量内存
    if (p->name != NULL) {
        free(p->name);
        p->name = NULL;
    }

    // 释放结构体指针变量内存
    if (p != NULL) {
        free(p);
        p = NULL;
    }

    return 0;
}


// 输出结果如下:
p->name = test, p->age=22
(*p).name = test, (*p).age=22
6、结构体做函数参数
  • 结构体的体积较大,占用的内存空间较多,往往不使用 传值 方式
  • 结构体作为函数的参数时,总是用 “传地址” 方式。如果只是输入参数,则加上 const 修饰
a、结构体普通变量做函数参数
#include <stdio.h>
#include <string.h>

// 结构体类型的定义
typedef struct tagStudent {
    char name[50];
    int age;
} Student;

// 函数参数为结构体普通变量
void set_stu(Student tmp) {
    printf("tmp.name = %s, tmp.age = %d\n", tmp.name, tmp.age);
    strcpy(tmp.name, "mike");
    tmp.age = 28;
    printf("tmp.name = %s, tmp.age = %d\n", tmp.name, tmp.age);
}

int main() {
    Student s = {"han", 18};

    set_stu(s); // 值传递
    printf("s.name = %s, s.age = %d\n", s.name, s.age);

    return 0;
}

// 输出结果如下:
tmp.name = han, tmp.age = 18
tmp.name = mike, tmp.age = 28
s.name = han, s.age = 18
b、结构体指针变量做函数参数
#include <stdio.h>
#include <string.h>

// 结构体类型的定义
typedef struct tagStudent {
    char name[50];
    int age;
} Student;

// 函数参数为结构体指针变量
void set_stu(Student *tmp) {
    printf("tmp.name = %s, tmp.age = %d\n", tmp->name, tmp->age);
    strcpy(tmp->name, "mike");
    tmp->age = 28;
    printf("tmp.name = %s, tmp.age = %d\n", tmp->name, tmp->age);
}

int main() {
    Student s = {"han", 18};

    set_stu(&s); // 地址传递,直接操作 s 的内存,所以其初始值会改变
    printf("s.name = %s, s.age = %d\n", s.name, s.age);

    return 0;
}




// 输出结果如下:
tmp.name = han, tmp.age = 18
tmp.name = mike, tmp.age = 28
s.name = mike, s.age = 28
c、结构体数组名做函数参数
#include <stdio.h>

// 结构体类型的定义
typedef struct tagStudent {
    char name[50];
    int age;
} Student;


void set_stu_pro(Student *tmp, int n) {
    for (int i = 0; i < n; i++) {
        sprintf(tmp->name, "name%d", i);  // 格式化赋值
        tmp->age = 20 + i;
        tmp++;
    }
}

int main() {
    Student s[3] = {0};
    set_stu_pro(s, 3); // 数组名传递,直接操作 s 的内存,所以其初始值会改变

    for (int i = 0; i < 3; i++) {
        printf("%s, %d\n", s[i].name, s[i].age);
    }

    return 0;
}



// 输出结果如下:
name0, 20
name1, 21
name2, 22
d、const 修饰结构体指针形参变量
// 结构体类型的定义
typedef struct tagStudent {
    char name[50];
    int age;
} Student;

// 地址不可变
void fun1(Student *const p) {
    //p = NULL; //err
    p->age = 10; //ok
}

// 值不可变
//void fun2(Student const* p)
void fun2(const Student *p) {
    p = NULL; //ok
    //p->age = 10; //err
}

// 地址和值均不可变
void fun3(const Student *const p) {
    //p = NULL; //err
    //p->age = 10; //err
}

III、构造类型之共用体

  • 允许在相同的内存位置存储不同的数据类型,但是任何时候只能有一个成员带有值,起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
  • 占用的总内存大小是共用体中内存最大的那个成员
  • 共用体变量的地址和它的各成员的地址都是同一地址
/* 共用体定义 */
union [tag]   // 共用体类型,可选,如果省略,此时共用体就只能使用一次
{
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];  // 一个或多个共用体变量,可选


/* 共用体示例 */
#include <stdio.h>
#include <string.h>

typedef union tagData {
    int i;
    float f;
    char str[20];
} Data;

int main() {
    // 定义共用体变量
    Data data;

    // 1、共用体变量中起作用的成员是最后一次存放的成员
    data.i = 10;
    data.f = 220.5;
    strcpy(data.str, "C Programming");

    printf("data.i : %d\n", data.i);
    printf("data.f : %f\n", data.f);
    printf("data.str : %s\n", data.str);

    // 2、共用体大小为最大成员类型的大小
    printf("%lu\n", sizeof(data));

    // 3、所有成员的首地址是一样的
    printf("%p, %p, %p\n", &(data.i), &(data.f), &(data.str));
    return 0;
}

// 输出结果如下:
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming  // 说明最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因
20
0x7ffcee51cdd0, 0x7ffcee51cdd0, 0x7ffcee51cdd0

IV、构造类型之枚举

  • 枚举:将枚举变量的值用枚举常量一一列举出来,枚举变量的值只限于列举出来的枚举常量;主要目的是增加程序的可读性,可用来描述状态量枚举常量相当于宏定义,可以直接使用
  • 格式:enum 类型名 {枚举常量表};,eg:enum week {Sun=7, Mon=1, Tue, Wed, Thu, Fri, Sat}; ,定义枚举类型 week,枚举常量的值分别为7、1、2、3、4、5、6,eg: enum week Today=Sun枚举变量 Today 的初始值为 7
  • 注意:枚举常量代表该 枚举类型的变量 可能取的值,编译系统为每个枚举常量指定一个整数值,默认状态下,这个整数就是所列举元素的序号,序号从 0 开始,每个枚举量都比前一个大 1,可通过初始化显式地给每个枚举量指定值
#include <stdio.h>

typedef enum tagWeekday {
    Sun = 7,
    Mon = 1,
    Tue,
    Wed,
    Thu,
    Fri,
    Sat
} Weekday;

int main() {
    // 直接使用枚举常量
    printf("Today is %d\n", Sat);
    
    // 定义枚举变量
    Weekday Today = Sun;
    printf("Today is %d\n", Today);

    // 改变枚举变量的值
    Today = Tue;
    printf("Today is %d\n", Today);

    return 0;
}


// 输出结果如下:
Today is 6
Today is 7
Today is 2
  • enum2str 以及对外接口的方式
#include <stdio.h>
#include "logolabel.h"

// 放在对外头文件中
typedef enum {
    SEDAN = 0,    // 	0-小轿车
    PICKUP,       //	1-皮卡
    BUS,          //	2-公交车
} EVideoStructureType;

// 放在源文件中,长度可以稍微大一点,方便以后扩展
static char VideoStructureTypeStr[20][20] = {
        "SEDAN",      // 	0-小轿车
        "PICKUP",     //	1-皮卡
        "BUS"         //	2-公交车
};


int main(int argc, char *argv[]) {
    // 1、类别比较少的用枚举对外开放,放在相应的头文件中,相应的字符串输出放在源文件中;同时输出 id 和 label
    EVideoStructureType id = BUS;  // 等价于 EVideoStructureType id = 2;
    char *label = VideoStructureTypeStr[id];  // enum2str
    printf("id is %d, str is \"%s\" \n", PICKUP, label);  // 枚举常量相当于宏定义,可以直接使用

    // 2、类别比较多的不用枚举对外开放,直接放在 label.h 中对外;同时输出 id 和 label
    int logo_id = 3;
    printf("id is %d, logo is \"%s\" \n", logo_id, vehlogo_label_list[logo_id]);
    return 0;
}

// 输出如下所示:
id is 1, str is "BUS" 
id is 3, logo is "奥迪|一汽-大众奥迪|奥迪A6|2013款" 

// logolabel.h 的内容, 长度可以稍微大一点,方便以后扩展
char vehlogo_label_list[5626][80]={
        "奥迪|一汽-大众奥迪|奥迪A3|2014款",
        "奥迪|一汽-大众奥迪|奥迪A4|2003款",
        "奥迪|一汽-大众奥迪|奥迪A4|2006款",
        "奥迪|一汽-大众奥迪|奥迪A6|2013款",
        "奥迪|一汽-大众奥迪|奥迪A6|2016款"
};

V、构造类型之数组、指针

VI、构造类型之类


二、运算符

  • 算术运算符: + - * / % ++ --

    • 在除法运算中,当两个操作数都是整数时,商也为整数,小数部分一律舍去
    • 如:1 / 2 = 0; 1.0 / 2 = 0.5;
      这里写图片描述
  • 关系运算符:>, >=, ==, !=Note: 由于有效数字的问题,浮点数进行相等比较时最好使用两数相减小于某一很小的阈值(eg:abs(a-b)<=0.0001

  • 逻辑运算符:逻辑与、或、非 &&, ||, !

  • 位运算符:按位与、或、非、异或、左移、右移 (&, |, ~, ^, <<, >>)一般不用于有符号的操作数上

    • 按位与运算: 通常用来对某些位 清 0,或者保留某些位。例如要把 n 的高 16 位清 0 ,保留低 16 位,可以进行 n & 0XFFFF运算(0XFFFF 在内存中的存储形式为 0000 0000 0000 0000 1111 1111 1111 1111)

    • 按位或运算: 可以用来将某些位 置 1,或者保留某些位。例如要把 n 的高 16 位置 1,保留低 16 位,可以进行n | 0XFFFF0000运算(0XFFFF0000 在内存中的存储形式为 1111 1111 1111 1111 0000 0000 – 0000 0000)

    • 按位异或运算: 可以用来将某些二进制位 反转

      #include <stdio.h>
      #include <stdlib.h>
      
      int main(){
          char plaintext = 'a';  // 明文
          char secretkey = '!';  // 密钥
          char ciphertext = plaintext ^ secretkey;  // 密文
          char decodetext = ciphertext ^ secretkey;  // 解密后的字符
          char buffer[9];
      
          printf("            char    ASCII\n");
          // itoa()用来将数字转换为字符串,可以设定转换时的进制(基数)
          // 这里将字符对应的ascii码转换为二进制
          printf(" plaintext   %c     %7s\n", plaintext, itoa(plaintext, buffer, 2));
          printf(" secretkey   %c     %7s\n", secretkey, itoa(secretkey, buffer, 2));
          printf("ciphertext   %c     %7s\n", ciphertext, itoa(ciphertext, buffer, 2));
          printf("decodetext   %c     %7s\n", decodetext, itoa(decodetext, buffer, 2));
      
          return 0;
      }
      
      // 结果输出如下:
                  char    ASCII
       plaintext   a     1100001
       secretkey   !      100001
      ciphertext   @     1000000
      decodetext   a     1100001
      
    • 左移运算符: 用来把操作数的各个二进制位全部左移若干位,高位丢弃,低位补 0,如果数据较小,被丢弃的高位不包含 1,那么左移 n 位相当于乘以 2 的 n 次方

      • a < < b a<<b a<<b 等价于 a ∗ ( 1 < < b ) a*(1<<b) a(1<<b),而 ( 1 < < b ) (1<<b) 1<<b的结果是二进制数: 100…00 (共 b 个0,表示将 1 向左移动 b 位),对应的十进制结果是: 2 b 2^b 2b
      • 示例: 3 < < 5 = 3 ∗ 2 5 = 96 3<<5 = 3*2^{5} = 96 3<<5=325=96
    • 右移运算符: 用来把操作数的各个二进制位全部右移若干位,低位丢弃,高位补 0 或 1。如果数据的最高位是 0,那么就补 0;如果最高位是 1,那么就补 1;如果被丢弃的低位不包含 1,那么右移 n 位相当于除以 2 的 n 次方(但被移除的位中经常会包含 1)

  • 条件运算符(三目运算符):

    • max = (x > y) ? x : y; 将x和y中较大的一个数赋值给变量 max,
    • eg:(i < 0) ? 0 : ((i > 255) ? 255 : i);roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x;
  • 赋值运算符:=, +=, -=, *=, /=, %=

  • 逗号运算符:经常使用在for循环语句中为 多个变量赋初值eg:for (i=0, j=n; i<j; i++, j--)

  • 运算符优先级:
    在这里插入图片描述

  • sizeof 运算符的用法:

    • 语法: sizeof(变量) or sizeof(数据类型)
    • sizeof 返回的是为这个变量开辟内存的大小,而不只是它用到的空间
    • sizeof 返回的数据结果类型是 unsigned int,如果要用于减法运算时最好强转为 int 型,eg:(int)sizeof(int)
    • 对数组名用 sizeof 返回的是整个数组的大小,而当数组作为函数参数时则会退化为指向数组首元素的指针,返回的则是指针变量本身所占得空间
/* sizeof 运算符的用法:sizeof(变量) or sizeof(数据类型) */

#include <iostream>
using namespace std;

int main()
{
   cout << "sizeof fundamental types:" << endl;
   cout << "sizeof(char) = " << sizeof(char) << endl;
   cout << "sizeof(int) = " << sizeof(int) << endl;
   cout << "sizeof(double) = " << sizeof(double) << endl;

   cout << "sizeof pointers to fundamental types:" << endl;
   cout << "sizeof(char*) = " << sizeof(char*) << endl;
   cout << "sizeof(int*) = " << sizeof(int*) << endl;
   cout << "sizeof(double*) = " << sizeof(double*) << endl;

   return 0;
}


sizeof fundamental types:
sizeof(char) = 1 
sizeof(int) = 4 
sizeof(double) = 8

// 将 sizeof() 用于指针时,结果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关
// 存储指针所需的内存量相同
sizeof pointers to fundamental types: 
sizeof(char*) = 4 
sizeof(int*) = 4 
sizeof(double*) = 4


char str[] = "hello";
printf("sizeof str:%d\n", sizeof(str));  // sizeof str:6,sizeof 计算数组大小,数组包含 '\0' 字符
printf("strlen str:%d\n", strlen(str));  // strlen str:5,strlen 计算字符串的长度,到 '\0' 结束

三、表达式

表达式:由常量、变量、函数调用和运算符按一定规律组合在一起构成的式子

1、变量:程序运行过程中其值可以变化的量

  • 编译器在编译程序时会将 变量名 看成一个 符号,符号值即变量的地址,各种不同的符号保存在 符号表 中。我们可以 通过变量名对和它绑定的内存单元进行读写, 而不是直接使用内存地址。 通过变量名访问内存, 既方便了程序的编写, 也大大增强了程序的可读性
  • 变量初始化基本操作:
    • 内存中 开辟空间,保存相应的数值
    • 编译器中 构造符号表,将标识符与相关内存空间关联起来

在这里插入图片描述

  • 变量的定义数据类型 变量名1, 变量名2, ... , 变量名n;
  • 变量的初始化数据类型 变量名 = 表达式;
  • 变量的引用数据类型& 引用名 = 已定义的变量; 即给一个已知变量起个别名,与原变量共享同一段内存,引用好比是一个虚拟的变量,此时对变量的引用就相当于取该地址区域上的内容
  • 变量的特点:
    • 变量在 编译 时才为其分配相应的内存空间
    • 可以通过 名字地址 来访问相应内存中的数据
    • eg: int a=10; printf("a is %d; *(&a) is %d \n", a, *(&a));
  • 变量的地址:
    • 程序每次运行时,变量的地址是不一样的,无法预测也不需要预测其地址,内存用于存储数据,最小单元是字节(1Byte=8bit),用 sizeof() 运算符取得变量所占内存的大小(以Byte为单位):
    • 1 K b = 2 10 B y t e ; 1 M b = 2 20 B y t e ; 1 G b = 2 30 B y t e ; 4 G b = 2 32 B y t e 1Kb = 2^{10} Byte; 1Mb = 2^{20} Byte; 1Gb = 2^{30} Byte; 4Gb = 2^{32} Byte 1Kb=210Byte;1Mb=220Byte;1Gb=230Byte;4Gb=232Byte,4GB 内存每个单元的都有一个编号,地址取值范围为: 0x00000000~0xFFFFFFFF
    • int 型变量,在 32 bit 的操作系统中通常占用 4 Byte(32bit=4Byte),在 64 bit 的操作系统中通常占用 8 Byte(64bit=8Byte)
  • 字符型变量:
    • 定义及初始化:char ch='A' // 单引号括起来
    • 字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的 ASCII 编码(整数0~127)放到变量的存储单元中
    • char 的本质就是一个 1 字节大小的整型
#include <stdio.h>

int main() {
    char ch = 'a';
    printf("sizeof(ch) = %u\n", sizeof(ch));     // 输出字符占用内存的大小
    printf("char a is: %c\n", ch);               // 打印字符
    printf("char a's ascii is: %d\n", ch);       // 打印字符的 ASCII 值
    
    ch = ' ';
    printf("char space's ascii is: %d\n", ch);   // 空字符 ASCII 的值为 32
    printf("A = %c\n", 'a' - ' ');               // 小写 a 转大写 A
    printf("a = %c\n", 'A' + ' ');               // 大写 A 转小写 a
    printf("A = %c\n", 'a' - 32);                // 小写 a 转大写 A
    printf("a = %c\n", 'A' + 32);                // 大写 A 转小写 a
    printf("A==65-->%d", 'A'==65);               // 验证字符 'A' 的本质是整数 65
    return 0;
}

// 输出结果如下:
sizeof(ch) = 1
char a is: a
char a's ascii is: 97
char space's ascii is: 32
A = A
a = a
A = A
a = a
A==65-->1
  • ASCII 表:
    在这里插入图片描述
  • 转义字符:
    • '\0'==0,ascii 码值为 0,代表 null
    • 在以字符格式(%c)在屏幕上打印时不会显示,以整数格式(%d)打印会输出 0
      在这里插入图片描述

2、常量:程序运行过程中其值不变的量

  • 在 C 语言中,通过内联方式直接写到源代码中的字面量值一般被称为 “常量”
    • 例如 int a = 10;,数字值 10 便为常量,其在被拷贝并赋值给相应的变量后便结束了使命
    • 通常用 define命令 定义符号常量, #define 标识符 常量值 // 宏定义 ,只是文本替换,数据类型未知
  • 伪常量:用 关键字const 定义符号常量(可以看做只读变量),形如:const 数据类型 标识符 = 常量值;
  • 只读变量(伪常量)与 字面量常量 的一个最重要的不同点是,使用 const 修饰的只读变量不具有常量表达式的属性,因此无法用来表示定长数组大小,或使用在 case 语句中
  • 常量表达式本身会在程序编译时被求值,而只读变量的值只能够在程序实际运行时才被得知;并且编译器通常不会对只读变量进行内联处理,因此其求值不符合常量表达式的特征。
  • C++11 后 constexpr 关键字:使用 constexpr 声明,声明的是编译期常量,编译器可以利用其进行优化

#include <stdio.h>
int main(void) {
  const int vx = 10;
  const int vy = 20;
  int arr[vx] = {1, 2, 3};  // [错误1] 使用非常量表达式定义定长数组;
  switch(vy) {
    case vx: {  // [错误2] 非常量表达式应用于 case 语句;
      printf("Value matched!");
      break;
    }
  }
}
  • 宏定义的妙用:
// 1、定义空的宏(不影响编译),提高程序的可读性,让代码更规范;可以在任何地方使用(结构体、函数等等)
#ifndef IN
#define IN
#endif

#ifndef OUT
#define OUT
#endif

#ifndef INOUT
#define INOUT
#endif

struct input_desc {
	INOUT const char *name;  /*!< Input name that is set by the app,
		* or returned in nnctrl_get_net_io_cfg() API */
	INOUT uint8_t *virt;  /*!< Input virtual address. Return virtual address if not set no_mem */
	INOUT uint32_t addr;  /*!< Input absolute physical address.
		* Set by APP if set no_mem, otherwise get from Library */
	OUT uint32_t size;  /*!< Input data size obtained from the library */

	IN uint32_t no_mem : 1;  /*!< Let library allocate the memory for input if it equals 0.
		* The app allocates the memory and specifies the address if it equals 1 */

	IN uint32_t rotate_flip_bitmap : 5;  /*!< Bitmap for fives fields, ordered High -> Low.
				* 4: pflip; 3: dflip; 2: vflip; 1: hflip; 0: rotate. */
	uint32_t reserved_0 : 2;
	uint32_t reserved_1 : 8;  /*!< Reserved field */
	IN uint32_t update_pitch : 16;  /*!< Run-time changes the pitch of the input.
		* If rotation is used when the input height and width is different, it needs to be changed.
		* Do not subtract one on the real pitch, as the library will subtract one internally.
		* Can not less than width size, at least be 32 byte align. */

	OUT struct io_dim dim;  /*!< The dimension information for the port */
	OUT struct io_data_fmt data_fmt;  /*!< The data format of the port */

	uint32_t reserved_2[20];  /*!< Reserved field */
};


int nnctrl_load_net(IN int net_id, IN struct net_mem *net_m,
	INOUT struct net_input_cfg *net_in, INOUT struct net_output_cfg *net_out);


2、定义一些常用的函数
#ifndef MIN
#define MIN(a, b)   ((a) > (b) ? (b) : (a))
#endif

#ifndef MAX
#define MAX(a, b)   ((a) > (b) ? (a) : (b))
#endif

#ifndef ABS
#define ABS(x) ((x) >= 0 ? (x) : (-(x)))
#endif
  • 类型限定符:
    在这里插入图片描述

  • register 变量必须是一个单个的值,并且其长度小于或等于整型的长度

  • register 变量不能使用取地址运算符 & 来获取其地址,因为它不存放在内存中

  • static 关键字:

    • 修饰变量存储在静态区,内存位置在程序执行期间一直不改变
      • 当用static修饰变量时,该变量就不能够在别的文件中用 extern 声明和访问了,只能在本文件内可见,生命周期和程序运行周期一样,且 staitc 局部变量的值 只会初始化一次(重复初始化无效),但可以多次赋值改变,离开作用域后内存也不会销毁,下次在作用域内仍能用到改变后的值(参见程序示例)
      • static 局部/全局变量的作用域在定义的 函数内/所定义的文件 中有效,不同文件静态全局变量可以重名(作用域不冲突)
      • static 局部/全局变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值 0,字符型变量赋空字符
    • 修饰函数:只能在声明它们的源文件中访问,不用担心与其它文件中的函数同名
      • 全局函数:在 C 语言中函数默认都是全局的,意味着所有函数都不能重名
      • 静态函数:使用关键字 static 可以将函数声明为静态,那么此函数就只能在当前文件中使用,在其他文件中不能调用,但不同的文件 static 函数名是可以相同的
#include <stdio.h>

// 局部变量作用域
int main()
{
   if(1)
   {
      // 在复合语句中定义,只在复合语句中有效
      int b = 123;        // b 生效
   }                      // 大括号结束时,b 已经失效
   
   printf("%d \n", b);    // 不可以访问一个已经失效的变量
        
   return 0;
}



// static:静态局部变量 
void fun1() {
    int i = 0;
    i++;
    printf("i = %d\n", i);
}

void fun2() {
    // 静态局部变量,没有赋值,系统赋值为 0,而且只会初始化一次(重复初始化无效)
    static int a = 5;
    a++;
    printf("a = %d\n", a);
}

int main(void) {
    fun1();  // i=1
    fun1();  // i=1
    fun2();  // a=6
    fun2();  // a=7
    fun2();  // a=8
    return 0;
}



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

int e;              // 全局未初始化变量,默认 0
int f = 10;         // 全局初始化变量
static int g;       // 静态全局未初始化变量,默认 0
static int h = 10;  // 静态全局初始化变量

int main() {
    int a;                   // 局部未初始化变量,默认随机值
    int b = 10;              // 局部初始化变量
    static int c;            // 静态局部未初始化变量,默认 0
    static int d = 10;       // 静态局部初始化变量
    const char* i = "test";  // 只读数据(文字常量区)
    char* k = NULL;
    k = (char*) malloc(10);  // 动态分配的内存
    return 0;
}

3、数值比较

  • 浮点数据比较
#include <stdio.h>
#include "math.h"

int main() {
    float a = 0.300000f;
    float b = 0.300001f;  
    printf("a is %f; b is %f\n", a, b);
    
    // 由于浮点 32 位精度只有小数点后 6 位,超过这些位数的就被截断了,所以一般用小于一个很小的值来判断等于或不等于
    if (fabs(a - b) < 1e-7) {
        printf("equal\n");
    } else {
        printf("not equal\n");
    }
    return (0);
}
  • 无符号数据与带符号数据之间的比较
#include <stdio.h>

int main() {
    int a = -1;
    unsigned int b = 1;

    // 隐式地将有符号参数强制类型为无符号数,来执行这个运算
    // 把 b 强制转换为有符号的数 (int)b 后便可以正常比较了
    if (a > b)
        // 输出 a > b, u_a = 4294967295, d_a = -1, b = 1
        printf("a > b, u_a = %u, d_a = %d, b = %u\n", a, a, b);
    else
        printf("a <= b, a = %d, b = %u\n", a, b);
    return 0;
}

四、参考资料

1、探索C++中的“{}初始化”:优雅与高效的结合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值