一、数据类型
I、数据类型概述
-
为什么要指明数据的类型?
- 更好进行内存的管理,让编译器能确定分配多少内存
- 规定数据所能进行的操作,限制非法运算(如:a%b , 若 a,b 为 float 类型则出错)
-
数据类型的转换:
- 显式类型转换: 常用于对函数的参数及返回值的操作,形式为:
(类型名)表达式;
或类型名(表达式);
或static_cast<类型名> (表达式) // C++
或dynamic_cast<类型名> (表达式) // C++,比静态强制转换更安全,转换失败会返回 nullptr
- 隐式类型转换:在不同类型数据进行混合运算时,系统自动进行的类型转换,一般按照从
低精度向高精度、 从有符号数向无符号数方向转换
(尤其注意此情况下可能出现的错误,最好先手动强转成相同类型)
- 显式类型转换: 常用于对函数的参数及返回值的操作,形式为:
-
有符号数与无符号数:
- 有符号数是最高位为符号位,0 代表正数,1 代表负数
- 无符号数最高位不是符号位,而就是数的一部分,无符号数不可能是负数
-
使用
auto
自动推断数据类型:- 若编译器支持 C++11 和更高版本,可不显式地指定变量的类型, 而使用关键字
auto
- 使用
auto
时必须对变量进行初始化,这样编译器才能根据初始值来确定变量的类型
- 若编译器支持 C++11 和更高版本,可不显式地指定变量的类型, 而使用关键字
-
使用
typedef
关键字可以将变量类型替换为其它好记的名称(加强程序的可移植性–>改一处即可):- 用法 1:为基本类型(int、float…)定义一个别名,eg:
typedef 数据类型 新的数据类型名; eg:typedef int INT;
- 用法 2:为自定义数据类型(数组、指针、结构体、共用体和枚举类型等构造类型)定义简洁的类型名称,
eg:typedef char* PCHAR;
- 和
#define
的区别:typedef
仅限于数据类型,而不是能是表达式或具体的值,#define
发生在预处理阶段,typedef
发生在编译阶段
- 用法 1:为基本类型(int、float…)定义一个别名,eg:
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: 指针结构体变量若暂时没有指向,可以先指向
NULL
或nullptr
// 指针结构体变量暂时没有指向
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) -
按位异或运算: 可以用来将某些二进制位
反转
- 例如要把 n 的高 16 位反转,保留低 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
- 例如要把 n 的高 16 位反转,保留低 16 位,可以进行
-
左移运算符: 用来把操作数的各个二进制位全部左移若干位,
高位丢弃,低位补 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=3∗25=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(变量)
orsizeof(数据类型)
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;
}