• [新手课堂] 面向对象究竟是什么鬼?该如何理解?
    基本所有的高级语言都支持面向对象编程(Object Oriented Programming, OOP),理解 OOP 的概念是程序员的基本要求;然而很多书籍对 OOP 的讲解都是照本宣科,把一个简单的概念搞的高深莫测、晦涩难懂,别说初学者,就连已经工作的初级程序员都不一定理解。很多书籍在讲解 OOP 时都这样写:类是对某一类事物的描述,是抽象的;对象是实际存在的,是该类事务的一个个体。例如,动物就是一种“类”,狗、猫、人、羊就是一种“对象”。这种说法完全正确,而且非常严谨,这就是类和对象的学术定义。但是,初学者真心看不懂,它用板砖拍自己脑门也想不明白,这究竟是个什么玩意。这就是典型的只说“官话”不说“人话”,恁娘。如何理解 OOP?在正式讲解 OOP 的概念之间,我们先来看两段代码,其中一段是C语言代码,没有使用 OOP,另外一段是 C++ 代码,使用了 OOP。C语言是常见的面向过程编程语言,C++ 是常见的面向对象编程语言,C++ 是在C语言的基础上发展起来的,C++ 完全兼容C语言代码,有C语言基础的读者也可以顺利地阅读下面的 C++ 代码。不使用 OOP 的C语言代码如下:#include ​ //定义变量 char *name; int age; float score; ​ //定义函数 void display(){   printf("%s的年龄是 %d,高考成绩是 %.1f\n", name, age, score); } ​ int main(){   //为变量赋值   name = "严长生";   age = 32;   score = 622;   //调用函数   display(); ​   return 0; }运行结果: 严长生的年龄是 32,高考成绩是 622.0使用了 OOP 的 C++ 代码:#include ​ //通过class关键字类定义类 class Student{ public:   //类包含的变量   char *name;   int age;   float score;   //类包含的函数   void display(){       printf("%s的年龄是 %d,高考成绩是 %.1f\n", name, age, score);   } }; ​ int main(){   //通过类来创建对象   Student stu1;   //为类中的变量赋值   stu1.name = "严长生";   stu1.age = 32;   stu1.score = 622;   //调用类中的函数   stu1.display(); ​   return 0; }运行结果与上例相同。在编程语言中,我们将变量看做数据,它用来存储多种形式的值;我们将函数看做操作,它用来对数据进行某些处理。所有的代码都由数据和操作构成,程序运行的本质就是对数据进行各种操作。在 OOP 出现以前,数据和操作是分离的,我们无法将相关的数据和操作在语法上聚合在一起,当代码量暴增以后,我们很难搞清楚哪份数据和哪个操作有关联,导致逻辑非常混乱,不利于大规模程序开发。另外,数据直接暴露在全局范围内,没有任何保护,和 A 操作有关的数据,也可以被 B 操作使用,这有可能导致程序员错误地使用数据。而有了 OOP 以后,我们就可以把相关的数据和操作放在一个容器中,这个容器就是类(class)。类把相关的变量和函数封装在一起,让它们从语法上发生关联,并且和其它类中的变量/函数隔离开,避免程序员误操作。类和类之间相互隔离这意味着,变量和函数只在当前类中有效,对其它类不可见,比如,A 类中的函数不能使用 B 类中的变量,反之亦然。OOP 其实就是一种代码封装思想,它将相关变量和函数放到一个类中,并将它们保护和隔离起来,形成一个一个的小模块,每个小模块能够完成一个小任务。OOP 在代码执行效率上绝对没有任何优势,它的主要目的是方便程序员组织和管理代码,快速梳理编程思路,带来编程思想上的革新。面向对象编程是针对开发中大规模的程序而提出来的,目的是提高软件开发的效率。不要把面向对象和面向过程对立起来,面向对象和面向过程不是矛盾的,而是各有用途、互为补充的。如果你希望开发一个贪吃蛇游戏,面向对象或许是多余的,几个函数就可以搞定;但如果开发一款大型游戏,那你绝对离不开面向对象。再谈类和对象通过上面的 C++ 代码可以发现,类不能直接使用,需要先通过类创建对象(object),然后再通过对象调用类中的变量和函数。其实,类只是一种复杂的、由用户自己构建的数据类型,它和整数(int)、小数(float)、字符串(string)等基本类型一样,必须先定义出变量才能使用,只不过通过类定义出来的变量有了一个新名字,叫做对象(object)。和普通变量相比,对象也有一些新特性,比如可以使用内部的普通变量和函数。可以将类比喻成图纸,将对象比喻成零件,图纸说明了零件的参数(成员变量)及其承担的任务(成员函数);一张图纸可以生产出多个具有相同性质的零件,不同图纸可以生产不同类型的零件。类只是一张图纸,起到说明的作用,不占用内存空间;对象才是具体的零件,要有地方来存放,才会占用内存空间。在 C++ 中,通过类名就可以创建对象,即将图纸生产成零件,这个过程叫做类的实例化,因此也称对象是类的一个实例(Instance)。有些教材也将类的成员变量称为属性(Property),将类的成员函数称为方法(Method)。总结OOP 是一个通用的概念,C++、Java、C#、PHP、Python 等很多高级编程语言都支持 OOP。面向对象是编程思想的一次重大革新,程序员们看到了面向过程的不足,尝试加以改善,让大家在开发和扩展中大型项目时更加容易。因为 C++、Java、C#、PHP、Python 等语言都支持类和对象,所以使用这些语言编写程序也被称为面向对象编程,这些语言也被称为面向对象的编程语言。C语言因为不支持类和对象的概念,被称为面向过程的编程语言。相比面向过程,面向对象增加了一层封装,就是类。不要小看类(Class)这一层封装,它有很多特性,极大地方便了中大型程序的开发。
  • [问题求助] LiteOS能够进行Fuzzing模糊测试吗?
    LiteOS能否使用fuzz编译器进行插桩然后使用qemu等虚拟机进行模糊测试,检测crash
  • [问题求助] 请求文档链接
    请问谁有《IPC V200R003C00 智能元数据结构设计说明书》?谢谢!
  • C字符串操作笔记-转载
     注意: 以下所有有关位置的参数,无论是C风格字符数组还是C++风格的字符串,首字符索引都为0。 下文无论是C风格字符数组还是C++风格的字符串,均统称为字符串。在需要区别时会特别指出。 使用C相关函数需调用string.h库。 在操作C风格字符数组时,请尽量使用C相关函数,而不是“+” “=”或关系运算符,这可能会导致无法预料的错误。  字符串字面量 定义 在程序中以明文展示的字符串,例如"123"、"AB\nCD"这种,被称为字符串字面量,也称字符串常量。  在C中,字符串字面量被视为字符数组:  printf("%d", sizeof("ABC"));  //输出4(3个字符+1个结尾的空字符) 1 因此,可对字符串字面量使用下标:  printf("%c", "ABC"[0]);    //输出A 1 存储与赋值 字符串字面量可以初始化数组的方式赋值给字符数组:  char s[4] = "123"; 1 此时字面量按字符数组的方式逐赋值给s。s的各项可以自由更改。  若将字符串字面量赋值给char*,编译器会开辟一块只读的内存空间给字符串字面量,然后将该内存的地址赋值给char*。 由于字面量被视为常量,对指针指向的内存地址进行修改是非法的,但由于指针本身为变量,反复令指针指向不同字面量的地址则是合法的。  char *s = "123"; *(s+1) = '4';      //非法 strcpy(s, "456");  //非法,因为对s指向的内存进行修改 s = "456";         //合法 1 2 3 4   获取长度 C size_t strlen(const char *str) //返回字符串的长度 1 注意:size_t一般被编译器定义为无符号int型,因此在计算字符串长度或别的需要对size_t类型的变量运算时,要注意避免给变量赋值为负数,这会导致不可预料的错误。  另外,strlen函数不会计算字符串末尾的空字符,但对字符数组名进行sizeof,会返回数组实际大小(也就是会算上末尾的空字符)。  C++ string s; s.size() 1 2  比较 单个字符(char类型)可直接使用>、<、==比较。  字符串(string、char []、char*类型)则需要调用相关函数。比较规则:自左向右逐个字符,按其对应的ASCII码的大小进行比较。若字符串长度不一致,则短字符串末尾之后被视为空字符进行比较(空字符的ASCII码为0)。  C 无论是char []或char*都必须使用函数strcmp或strncmp,直接用>、<、==可能导致结果不准。  int strcmp(const char *str1, const char *str2)  //比较两个字符串 int strncmp(const char *str1, const char *str2, size_t n)  //比较两个字符串的前n位 1 2 3 4 若相等,则返回零;若str1str2,则返回正数。  C++ 直接>、<、==即可,或者:  string str1, str2; str1.compare(str2);    str1.compare(pos1,len1,str2,pos2,len2); 1 2 3   查找 C char *strchr(const char *str, int c) //找到字符c在str中首次出现的位置p,返回str中从第p位开始的所有字符。找不到返回NULL。 char *strrchr(const char *str, int c) //找到字符c在str中最后出现的位置p,返回str中从第p位开始的所有字符。找不到返回NULL。  char *strstr(const char *s1, const char *s2) //找到字符串s2在s1中首次出现的位置p,返回s1中从第p位开始的所有字符。找不到返回NULL。 1 2 3 4 5 6 7 C++ str1.find(str2);  //返回一个整数,表示str2在str1中第一次出现的位置,若没有返回-1 str1.rfind(str2); //同上,但从后往前找 1 2   清除或填充 C memset(void *p, int c, size_t n) 1 从p指向的地址开始,对接下来的连续n个字节的内存空间,逐字节置为c。对于字符串,n为字符串长度乘以sizeof(char);对于数组,n为元素数量乘以每个元素的字节数。  对于int型数组,数组的单个元素占4个字节,若c=1,逐个字节填充后数组中单个元素会被置为:  00000001 00000001 00000001 00000001  转成十进制是16843009。  因此,若要用memset初始化整型数组,仅0和-1可起到预期效果。  C++ string s; s.clear();//全部清除 s.erase(pos,len); 1 2 3   修改 C char *strcat(char * s1, const char *s2) //在s1末尾添加s2,返回值为指向s1的指针 char *strncat(char *s1, const char *s2, size_t n) //将s2的前n位添加到s1末尾,返回值为指向s1的指针  char *strcpy(char *s1, const char *s2) //将s1替换为s2,返回指向s1的指针 char *strncpy(char *s1, const char *s2, size_t n) //将s1的前n位替换为s2的前n位,返回指向s1的指针。若s2不足n位,用空字符填充 1 2 3 4 5 6 7 8 9 此外,也可以使用memcpy,直接从内存中复制对应值实现对字符串的修改。  C++ str1.append(str2);           //与str1+=str2等效 str1.append(str2,pos,len);   //在str1的末尾加上str2的第pos位开始的连续len个字符 //若要连接字符串,直接使用+即可。  str1.insert(pos1,str2);   str1.insert(pos1,str2,pos2,len2);   str1.insert(pos1,num,char);   // num是插入次数,char是要插入的字符。  str2 = str1.substr(pos);     //将str1的第pos位及之后的所有字符赋值给str2 str2 = str1.substr(pos,len); //将str1从第pos位开始连续len个字符赋值给str2  str1.replace(pos,len,str2);  //把str1从第pos位开始的连续len位替换成str2 str1.replace(pos1,len1,str2,pos2,len2); //多出的两个参数指定了str2参与替换的部分  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18   格式转换 C与C++转换 C字符串可直接赋值给C++字符串。  C++字符串s转C字符串c:  strcpy(c, s.c_str()); 1 字符串转数字 C 使用sscanf函数,方法与参数与scanf类似,区别在于scanf从设备(键盘)中取得内容放入变量,而sscanf从str中获得内容放入变量。  int sscanf(const char *str, const char *format, ...)  //用法举例 int d; char s[5] = "123"; sscanf(s, "%d", &d); 1 2 3 4 5 6 该方法支持整数、小数及多种进制类型,通过格式控制符format来操作。  C++: #include  ... string str = "123.45"; double d; stringstream ss; ss << str; ss >> d; 1 2 3 4 5 6 7 数字转字符串 C 使用sprintf函数,方法与参数与printf类似,区别在于printf将变量内容输出到屏幕,而sprintf将变量内容输出到str。  int sprintf(char *str, const char *format, ...)  //用法举例 int d = 123; char s[5]; sprintf(s, "%d", d); 1 2 3 4 5 6 该方法支持整数、小数及多种进制类型,通过格式控制符format来操作。  C++:(同样支持整数和实数) #include  ... double a=12.34; string str; stringstream ss; ss << a; ss >> str; ———————————————— 版权声明:本文为CSDN博主「Zerg Wang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Zerg_Wang/article/details/126667629 
  • [问题求助] Liteos内核编入video驱动后,系统启动后没有加载到。
    将video编译进内核,同样uvc支持等也编译了。系统起来后没有该驱动。启动log RTOS # random dev init ... MMC dev init ...sdhci0: LOSCFG_PLATFORM_GK7202V300 selected sdhci0-slot0: 150MHz HS 4bits VDD: 3.3V 3.0V VCCQ: 3.3V DRV: BACD DMA removable sdhci1: LOSCFG_PLATFORM_GK7202V300 selected sdhci1-slot1: 50MHz HS 4bits VDD: 3.3V 3.0V VCCQ: 3.3V DRV: BACD DMA non-removable sdhci1-slot1: Re-tuning count 0 secs, mode 1 mem dev init ... porc fs init ... Mount procfs finished. gpio init ... nand init ... fmc100_host_init(876): Error:Flash type isn't SPI Nand! spi nor flash init ... Spi Nor ID:0xEF 0x40 0x18 0x00 0x00 0x00 0x00 0x00  Spi Nor Flash Info: Name:"W25Q128(B/F)V" Size:16MB Block:64KB net init ...  ************************************************************ usb init ...  ******** usb_init in ********** usb v3.05 2019-10-22 09:32 Entering function [udc3_probe] mmc: Probing bus mmc: Current OCR: 0x00000000 mmc: No compatible cards found on bus   ** uvc device initialized successfully! **   ** composite attach success ** ******** usb_init ok**********  uvc init ... [CLX DBG] sdk/sample/liteos/app_init.c app_init ... line:324 g_sys_mem_addr_end=0x0x41e00000, Date:Jul 12 2022. Time:17:41:39. [CLX DBG] SDK_init ==== online_flag=0, cmos_yuv_flag=0, sensor=gc2053, chip=gk7202v300, board=sck==== ==== g_quick_start_flag=0 ==== sysconfig init success! osal_proc_mkdir - parent is NULL! proc=0x406082f8  g_mmz_start=0x42000000, g_mmz_size=0x20 mmz param= anonymous,0,0x42000000,32M <6>Vendor Media Memory Zone Manager load sys.ko OK! load region.ko OK! load vgs.ko OK! load isp.ko OK ! load vi.ko OK ! load vpss.ko OK! load vo.ko OK! load chnl.ko OK! load vedu.ko OK! load rc.ko OK! load venc.ko OK! load h264e.ko OK! load h265e.ko OK! load jpege.ko OK! load ive.ko OK! load aio.ko ....OK! load ai.ko ....OK! load ao.ko ....OK! load aenc.ko ....OK! load adec.ko ....OK! load acodec.ko ....OK! insert audio spi bus init ... i2c bus init ... load mipi_rx driver successful! sensor i2c init OK. load tde.ko OK! Load gfbg.ko OK! pm init ok! [CLX DBG] SDK init ok... [CLX DBG] sdk/sample/liteos/app_init.c app_init ... line:332 [CLX DBG] sdk/sample/liteos/app_init.c app_sample line:174 camera_Task 7:0 [CLX DBG] sdk/sample/liteos/app_init.c app_sample line:186  input command: sdk_test -bulkmode -uvc -off -ivp -relay [CLX DBG] sdk/sample/liteos/app_init.c com_app line:101  @@@@@ UVC App Sample @@@@@  lixiang test! open_uvc_device  644 waiting for UAC connect! Waiting for USB connection... 
  • [技术干货] 利用Python和C语言分别实现哈夫曼编码【转】
    目录1.C语言实现1.1代码说明1.2运行结果2.Python实现2.1代码说明2.2运行结果1.C语言实现1.1代码说明a  创建双向链表:在创建哈夫曼树的过程中,需要不断对结点进行更改和删除,所以选用双向链表的结构更容易123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102'''C#include <stdlib.h>#include <stdio.h>#include <windows.h>    //哈夫曼树结构体,数据域存储字符及其权重typedef struct node{    char c;    int weight;    struct node *lchild, *rchild;}Huffman, *Tree;    //双向链表结构体,数据域存储哈夫曼树结点typedef struct list{    Tree root;    struct list *pre;    struct list *next;}List, *pList;    //创建双向链表,返回头结点指针pList creatList(){    pList head = (pList)malloc(sizeof(List));      pList temp1 = head;    pList temp2 = (pList)malloc(sizeof(List));    temp1->pre = NULL;    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'a';    temp1->root->weight = 22;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;           temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'b';    temp1->root->weight = 5;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;           temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'c';    temp1->root->weight = 38;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'd';    temp1->root->weight = 9;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'e';    temp1->root->weight = 44;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'f';    temp1->root->weight = 12;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp1->next = NULL;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'g';    temp1->root->weight = 65;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      return head;                          }b创建栈结构:解码过程需要用到两个栈,一个用来存放树结点,一个用来存放码0和1123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115'''C#define STACK_INIT_SIZE 100   //栈初始开辟空间大小#define STACK_INCREMENT 10    //栈追加空间大小  //字符栈结构体,存放编码'0'和'1'typedef struct {    char *base;    char *top;    int size;}charStack;    //栈初始化charStack charStackInit(){    charStack s;    s.base = (char *)malloc(sizeof(char)*STACK_INIT_SIZE);    s.top = s.base;    s.size = STACK_INIT_SIZE;    return s;}  //入栈void charPush(charStack *s, char e){    if(s->top - s->base >= s->size)    {        s->size += STACK_INCREMENT;        s->base = realloc(s->base, sizeof(char)*s->size);    }    *s->top = e;    s->top++;}  //出栈char charPop(charStack *s){    if(s->top != s->base)    {        s->top--;        return *s->top;    }    return -1;}  //得到栈顶元素,但不出栈char charGetTop(charStack *s){    s->top--;    char temp = *s->top;    s->top++;    return temp;}  //栈结构体,存放哈夫曼树结点typedef struct{    Huffman *base;    Huffman *top;    int size;}BiStack;  //栈初始化BiStack stackInit(){    BiStack s;    s.base = (Huffman *)malloc(sizeof(Huffman)*STACK_INIT_SIZE);    s.top = s.base;    s.size =STACK_INIT_SIZE;    return s;}  //入栈void push(BiStack *s, Huffman e){    if(s->top - s->base >= s->size)    {        s->size += STACK_INCREMENT;        s->base = (Huffman *)realloc(s->base, sizeof(Huffman)*s->size);    }    *s->top = e;    s->top++;}  //出栈Huffman pop(BiStack *s){    Huffman temp;    s->top--;    temp = *s->top;    return temp;}  //得到栈顶元素,但不出栈Huffman getTop(BiStack s){    Huffman temp;    s.top--;    temp = *s.top;    return temp;}  char stack[7][10];             //记录a~g的编码//遍历栈,得到字符c的编码void traverseStack(charStack s, char c){    int index = c - 'a';     int i = 0;    while(s.base != s.top)    {        stack[index][i] = *s.base;        i++;        s.base++;    }}c 创建哈夫曼树:123456789101112131415161718192021222324252627282930313233343536373839404142434445'''C//通过双向链表创建哈夫曼树,返回根结点指针Tree creatHuffman(pList head){    pList list1 = NULL;    pList list2 = NULL;    pList index = NULL;    Tree root = NULL;    while(head->next != NULL)   //链表只剩一个结点时循环结束,此结点数据域即为哈夫曼树的根结点    {        list1 = head;        list2 = head->next;        index = list2->next;        root = (Tree)malloc(sizeof(Huffman));        while(index != NULL)    //找到链表中权重最小的两个结点list1,list2        {            if(list1->root->weight > index->root->weight || list2->root->weight > index->root->weight)            {                if(list1->root->weight > list2->root->weight) list1 = index;                else list2 = index;            }            index = index->next;        }        //list1和list2设为新结点的左右孩子        if(list2->root->weight > list1->root->weight)        {            root->lchild = list1->root;            root->rchild = list2->root;        }        else        {            root->lchild = list2->root;            root->rchild = list1->root;        }        //新结点字符统一设为空格,权重设为list1与list2权重之和        root->c = ' ';        root->weight = list1->root->weight + list2->root->weight;        //list1数据域替换成新结点,并删除list2        list1->root = root;        list2->pre->next = list2->next;        if(list2->next != NULL)            list2->next->pre = list2->pre;        }    return head->root;}d编码:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869'''Cchar stack[7][10];             //记录a~g的编码//遍历栈,得到字符c的编码void traverseStack(charStack s, char c){    int index = c - 'a';     int i = 0;    while(s.base != s.top)    {        stack[index][i] = *s.base;        i++;        s.base++;    }}    //通过哈夫曼树编码void encodeHuffman(Tree T){      BiStack bs = stackInit();    charStack cs = charStackInit();    Huffman root = *T;      Tree temp = NULL;    push(&bs, root);      //根结点入栈    while(bs.top != bs.base)      //栈空表示遍历结束    {        root = getTop(bs);        temp = root.lchild;       //先访问左孩子        while(temp != NULL)       //左孩子不为空        {            //将结点左孩子设为空,代表已访问其左孩子            root.lchild = NULL;            pop(&bs);                        push(&bs, root);            //左孩子入栈            root = *temp;            temp = root.lchild;            push(&bs, root);            //'0'入字符栈            charPush(&cs, '0');        }        temp = root.rchild;     //后访问右孩子             while(temp == NULL)     //右孩子为空,代表左右孩子均已访问,结点可以出栈         {            //结点出栈            root = pop(&bs);            //寻到叶子结点,可以得到结点中字符的编码            if(root.c != ' ')                traverseStack(cs, root.c);            charPop(&cs);       //字符栈出栈            if(bs.top == bs.base) break;    //根结点出栈,遍历结束            //查看上一级结点是否访问完左右孩子              root = getTop(bs);            temp = root.rchild;                   }        if(bs.top != bs.base)        {            //将结点右孩子设为空,代表已访问其右孩子            root.rchild = NULL;                   pop(&bs);            push(&bs, root);            //右孩子入栈            root = *temp;                  push(&bs, root);            //'1'入字符栈            charPush(&cs, '1');        }        }}e解码:1234567891011121314151617181920212223242526'''Cchar decode[100];   //记录解码得到的字符串//通过哈夫曼树解码void decodeHuffman(Tree T, char *code){    int cnt = 0;    Tree root;    while(*code != '\0')                  //01编码字符串读完,解码结束    {        root = T;        while(root->lchild != NULL)       //找到叶子结点        {            if(*code != '\0')            {                if(*code == '0')                    root = root->lchild;                else                    root = root->rchild;                code++;            }            else break;        }        decode[cnt] = root->c;             //叶子结点存放的字符即为解码得到的字符        cnt++;    }}f主函数:123456789101112131415161718192021222324'''Cvoid main(){    pList pl = creatList();    printf("字符的权重如下\n");    for(pList l = pl; l->next != NULL; l = l->next)        printf("字符%c的权重是 %d\n", l->root->c, l->root->weight);    Tree T = creatHuffman(pl);    encodeHuffman(T);    printf("\n\n字符编码结果如下\n");    for(int i = 0; i < 7; i++)        printf("%c : %s\n", i+'a', stack[i]);    char code[100];    printf("\n\n请输入编码:\n");    scanf("%s", code);    printf("解码结果如下:\n");    decodeHuffman(T, code);    printf("%s\n", decode);    printf("\n\n");    system("date /T");    system("TIME /T");    system("pause");    exit(0); }2.Python实现2.1代码说明a创建哈夫曼树:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#coding=gbk  import datetimeimport timefrom pip._vendor.distlib.compat import raw_input  #哈夫曼树结点类class Huffman:    def __init__(self, c, weight):        self.c = c        self.weight = weight        self.lchild = None        self.rchild = None         #创建结点左右孩子        def creat(self, lchild, rchild):        self.lchild = lchild        self.rchild = rchild  #创建列表        def creatList():    list = []    list.append(Huffman('a', 22))    list.append(Huffman('b', 5))    list.append(Huffman('c', 38))    list.append(Huffman('d', 9))    list.append(Huffman('e', 44))    list.append(Huffman('f', 12))    list.append(Huffman('g', 65))    return list  #通过列表创建哈夫曼树,返回树的根结点def creatHuffman(list):    while len(list) > 1:               #列表只剩一个结点时循环结束,此结点即为哈夫曼树的根结点        i = 0        j = 1        k = 2        while k < len(list):           #找到列表中权重最小的两个结点list1,list2                      if list[i].weight > list[k].weight or list[j].weight > list[k].weight:                if list[i].weight > list[j].weight:                    i = k                else:                    j = k            k += 1              root = Huffman(' ', list[i].weight + list[j].weight) #新结点字符统一设为空格,权重设为list1与list2权重之和           if list[i].weight < list[j].weight:                  #list1和list2设为新结点的左右孩子            root.creat(list[i], list[j])        else:            root.creat(list[j], list[i])        #list1数据域替换成新结点,并删除list2        list[i] = root        list.remove(list[j])    return list[0]b编码:12345678910111213141516171819202122232425262728293031323334353637#通过哈夫曼树编码def encodeHuffman(T):    code = [[], [], [], [], [], [], []]    #列表实现栈结构    treeStack = []    codeStack = []    treeStack.append(T)    while treeStack != []:        #栈空代表遍历结束        root = treeStack[-1]        temp = root.lchild        while temp != None:            #将结点左孩子设为空,代表已访问其左孩子            root.lchild = None                   #左孩子入栈                      treeStack.append(temp)                     root = temp            temp = root.lchild            #0入编码栈            codeStack.append(0)        temp = root.rchild            #后访问右孩子        while temp == None:           #右孩子为空,代表左右孩子均已访问,结点可以出栈            root = treeStack.pop()           #结点出栈            #寻到叶子结点,可以得到结点中字符的编码            if root.c != ' ':                codeTemp = codeStack.copy()                code[ord(root.c) - 97] = codeTemp                 if treeStack == []:    #根结点出栈,遍历结束                break            codeStack.pop()        #编码栈出栈            #查看上一级结点是否访问完左右孩子            root = treeStack[-1]            temp = root.rchild        if treeStack != []:            treeStack.append(temp)     #右孩子入栈            root.rchild = None         #将结点右孩子设为空,代表已访问其右孩子            codeStack.append(1)        #1入编码栈    return code c解码:1234567891011121314151617#通过哈夫曼树解码def decodeHuffman(T, strCode):    decode = []    index = 0    while index < len(strCode):        #01编码字符串读完,解码结束        root = T        while root.lchild != None:     #找到叶子结点            if index < len(strCode):                if strCode[index] == '0':                    root = root.lchild                else:                    root = root.rchild                index += 1            else:                break        decode.append(root.c)           #叶子结点存放的字符即为解码得到的字符    return decoded主函数:12345678910111213141516171819202122232425if __name__ == '__main__':    list = creatList()    print("字符的权重如下")    for i in range(len(list)):        print("字符{}的权重为: {}".format(chr(i+97), list[i].weight))    T = creatHuffman(list)    code = encodeHuffman(T)    print("\n字符编码结果如下")    for i in range(len(code)):        print(chr(i+97), end=' : ')        for j in range(len(code[i])):            print(code[i][j], end='')        print("")    strCode = input("\n请输入编码:\n")    #哈夫曼树在编码时被破坏,必须重建哈夫曼树    list = creatList()    T = creatHuffman(list)    decode = decodeHuffman(T, strCode)    print("解码结果如下:")    for i in range(len(decode)):        print(decode[i], end='')    print("\n\n")    datetime = datetime.datetime.now()    print(datetime.strftime("%Y-%m-%d\n%H:%M:%S"))    input("Press Enter to exit…")
  • [技术干货] C++常见面试题总结[转载]
    1、C和C++的区别1)C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。2)C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。3)C++中有引用,C中不存在引用的概念2、C++中指针和引用的区别1)指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的2)引用只有一级,而指针可以有多级3)指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作引用传参的时候,传进来的就是变量本身,因此变量可以被修改3、结构体struct和共同体union(联合)的区别结构体:将不同类型的数据组合成一个整体,是自定义类型共同体:不同类型的几个变量共同占用一段内存1)结构体中的每个成员都有自己独立的地址,它们是同时存在的;共同体中的所有成员占用同一段内存,它们不能同时存在;2)sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度、结构体为什么要内存对齐呢?1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常2.硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。4、#define和const的区别1)#define定义的常量没有类型,所给出的是一个立即数;const定义的常量有类型名字,存放在静态区域2)处理阶段不同,#define定义的宏变量在预处理时进行替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有一个拷贝。3)#define定义的常量是不可以用指针去指向,const定义的常量可以用指针去指向该常量的地址4)#define可以定义简单的函数,const不可以定义函数5、重载overload,覆盖(重写)override,隐藏(重定义)overwrite,这三者之间的区别1)overload,将语义相近的几个函数用同一个名字表示,但是参数列表(参数的类型,个数,顺序不同)不同,这就是函数重载,返回值类型可以不同特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无2)override,派生类覆盖基类的虚函数,实现接口的重用,返回值类型必须相同特征:不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字(必须是虚函数)3)overwrite,派生类屏蔽了其同名的基类函数,返回值类型可以不同特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字6、new、delete、malloc、free之间的关系new/delete,malloc/free都是动态分配内存的方式1)malloc对开辟的空间大小严格指定,而new只需要对象名2)new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数既然有了malloc/free,C++中为什么还需要new/delete呢?运算符是语言自身的特性,有固定的语义,编译器知道意味着什么,由编译器解释语义,生成相应的代码。库函数是依赖于库的,一定程度上独立于语言的。编译器不关心库函数的作用,只保证编译,调用函数参数和返回值符合语法,生成call函数的代码。malloc/free是库函数,new/delete是C++运算符。对于非内部数据类型而言,光用malloc/free无法满足动态对象都要求。new/delete是运算符,编译器保证调用构造和析构函数对对象进行初始化/析构。但是库函数malloc/free是库函数,不会执行构造/析构。7、delete和delete[]的区别delete只会调用一次析构函数,而delete[]会调用每个成员的析构函数用new分配的内存用delete释放,用new[]分配的内存用delete[]释放一.构造函数构造函数是和类名相同的一个函数,它的作用是实现对象的初始化。当对象被创建时,构造函数自动被调用。特点:没有类型没有返回值(也不用写void)名字与类名相同可重载!作用:完成类的对象的初始化Cdate d; //定义对象d注意:当对象d被创建时,会自动调用构造函数 d.Cdate()。当类中未定义构造函数时,编译器会自动假设存在以下两个默认构造函数:(此构造函数什么都不做,就是个形式)。如果作者自己定义了构造函数,则默认的构造函数不会存在。//默认构造函数一 Cdate::Cdate() { } //默认构造函数二 Cdate::Cdate(const Cdate& a) { }三.析构函数我们已经知道构造函数是在创建对象时,对其进行初始化。而析构函数与其相反,是在对象被删除前象由系统自动执行它做清理工作。作为一个类,可能有多个对象,每个对象生命结束时都要调用析构函数,且每个对象调用一次。特点:无类型无返回值名字与类名相同不带参数,不可重载,析构函数只有一个!析构函数前“~” (取反符,表示逆构造函数)作用:在对象被删除前做清理工作。注意:对象的析构函数在对象被销毁前被调用,对象何时销毁也与其作用域相关。例如,全局对象是在程序运行结束时销毁;自动对象是在离开其作用域时销毁;而动态对象是在使用delete运算符时销毁。析构函数特别适用于当一个对象被动态分配内存空间,而在对象被销毁前希望释放它所占用的内存空间的时候。我们不会忽略初始化的重要性,却常常忽略清除的重要性,然而对销毁变量的内存清理是非常重要的。例如,我们在堆中申请了一些内存,如果没有用完就释放,会造成内存泄露,会导致应用程序运行效率降低,甚至崩溃,不可掉以轻心。而在c++中提供有析构函数,可以保证对象清除工作自动执行。析构与构造的调用次序相反,即最先构造的最后被析构,最后构造的最先被析构。7.1、虚函数、纯虚函数虚函数:虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数,是C++中多态性的一个重要体现。利用基类指针访问派生类中的虚函数,这种情况下采用的是动态绑定技术。纯虚函数:纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”.纯虚函数不能实例化对象。抽象类的介绍抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。(1)抽象类的定义: 称带有纯虚函数的类为抽象类。(2)抽象类的作用: 抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。(3)使用抽象类时注意:抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。抽象类是不能定义对象的。总结:1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。2、虚函数声明如下:virtual ReturnType FunctionName(Parameter) 虚函数必须实现,如果不实现,编译器将报错,错误提示为:3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。纯虚函数的引入,是出于两个目的:1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。2、为了效率,不是程序执行的效率,而是为了编码的效率。动态绑定:基类指针是调用派生类的中的成员函数还是调用基类中的成员函数要到程序运行时确定。主要看此时基类指针所指向的对象。 这里要涉及一些很重要的概念,也是我最近看完Effective C++才明白的东西,记录下来。这些概念就是静态类型和动态类型,静态绑定和动态绑定。静态绑定和动态绑定。静态绑定是说前期绑定。 所谓对象的静态类型,就是它在程序中被声明的时候采用的类型。 考虑下面的class继承体系:class Shape{ virtual void draw(color = Red) const=0; ... ... }; class Rectangle:public Shape{ virtual void draw(color = Red) const; ... ... }; class Circle:public Shape { virtual void draw(color = Red) const;   ... ... }; 现在考虑以下这些指针: Shape* ps;//静态类型为Shape* Shape*pc =new Circle;//静态类型Shape* Shape*pr = new Rectangle;//静态类型Shape*在本例中,ps,pc,pr都被声明为Shape*类型的,所以它们的静态类型都是Shape*。注意:无论它们真正指向什么,它们的静态类型都是Shape*. 所谓的对象的动态类型是指“当前所指对象的类型”。也就是说,动态类型可以表现出一个对象将会有什么行为。根据上面的例子,pc的动态类型是Circle*,pr的动态类型是Rectangle*。ps没有动态类型,因为它没有指向任何对象。 动态类型一如其名所示,可以在执行过程中改变(通常是经过赋值运算):ps=pc; \\ps的动态类型如今是Circle* ps=pr; \\ps的动态类型如今是Rectangle*Virtual函数系动态绑定而来,意思是调用一个virtual函数的时候,究竟调用的是哪一个函数代码,取决于发出调用的那个对象的动态类型。ps->draw(); \\调用的是Rectangle::draw(Red)8、STL库用过吗?常见的STL容器有哪些?算法用过几个?STL包括两部分内容:容器和算法容器即存放数据的地方,比如array, vector,分为两类,序列式容器和关联式容器序列式容器,其中的元素不一定有序,但是都可以被排序,比如vector,list,queue,stack,heap, priority-queue, slist关联式容器,内部结构是一个平衡二叉树,每个元素都有一个键值和一个实值,比如map, set, hashtable, hash_set算法有排序,复制等,以及各个容器特定的算法迭代器是STL的精髓,迭代器提供了一种方法,使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。Vector是顺序容器,是一个动态数组,支持随机存取、插入、删除、查找等操作,在内存中是一块连续的空间。在原有空间不够情况下自动分配空间,增加为原来的两倍。vector随机存取效率高,但是在vector插入元素,需要移动的数目多,效率低下。注意:vector动态增加大小时,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。————————————————版权声明:本文为CSDN博主「Cpp编程小茶馆」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/xu_fu_yong/article/details/122948379
  • [技术干货] 选择排序(c语言)[转载]
    一、什么是选择排序? 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的中数据元素选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。二、选择排序思路首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。三、排序过程例:定义一个数组 int a[8] = {9,3,7,2,6,1,5,8},要求利用选择排序的方法将数组从小到大排序。排序的次数:因为每排好一个元素,那么所需要排的元素个数减一,直到排到倒数第二个元素停止,将倒数第二个元素也排好后,整体数组排序就完成了。所以排序的次数 = 元素个数 - 1。(冒泡排序的排序次数与该排序的排序次数计算方法相同)9,3,7,2,6,1,5,8第一次排序:假设首元素作为整体元素数据最小值,然后从该元素的后一个元素开始每个元素都与该最小值进行比较,假如有比该元素小的值,就用一个变量去记住下标值,最后比较完成后,把两个元素互换位置即可。第一次排序结果:{1,3,7,2,6,9,5,8}1,3,7,2,6,9,5,8第二次排序:因为第一次排序选择的是将首元素作为最小值,最终经过互换位置,首元素排序完成,第二次排序就不需要排序首元素,只需要排序除首元素以外的元素,然后在依照第一次排序的原理进行排序。第二次排序结果:{1,2,7,3,6,9,5,8}然后根据第一次排序和第二次排序的原理,最终的排序结果为:{1,2,3,5,6,7,8,9}四、代码的实现#include <stdio.h> void arr_out(int a[8])//输出函数{    int i = 0;    for(i = 0;i < 8;i++)    {        printf("%d ",a[i]);    }    printf("\n");} void arr_sort(int *p,int n){    int i,j;    int min = 0;    for(i = 0;i < n - 1;i++)//排序次数    {        min = i;        for(j = i + 1;j < n;j++)        {            if(p[j] < p[min])            {                min = j;//记录交换的元素下标值            }        }        if(i != min)        {            int temp = p[i];            p[i] = p[min];            p[min] = temp;        }      }} int main(){    int a[8] = {0};    int i = 0;    for(i = 0;i < 8;i++)    {        scanf("%d",&a[i]);    }     arr_sort(a,8);//排序函数    arr_out(a);//输出函数     return 0;}原文链接:https://blog.csdn.net/m0_59083833/article/details/123971321
  • [技术干货] GPS原始坐标转百度地图坐标(纯C代码)
    ## 一、环境介绍 **GPS模块型号:** 中科微电子GPS模块 **GPS输出的原始数据帧:** ```cpp $GNGGA,114955.000,2842.4158,N,11549.5439,E,1,05,3.8,54.8,M,0.0,M,,*4F $GNGLL,2842.4158,N,11549.5439,E,114955.000,A,A*4D $GPGSA,A,3,10,31,18,,,,,,,,,,5.7,3.8,4.2*37 $BDGSA,A,3,07,10,,,,,,,,,,,5.7,3.8,4.2*2A $GPGSV,3,1,10,10,49,184,42,12,16,039,,14,54,341,,18,22,165,23*7B $GPGSV,3,2,10,22,11,318,,25,51,055,,26,24,205,,29,13,110,*7C $GPGSV,3,3,10,31,50,287,36,32,66,018,*7F $BDGSV,1,1,04,03,,,07,05,,,29,07,79,246,33,10,52,232,19*62 $GNRMC,114955.000,A,2842.4158,N,11549.5439,E,0.00,44.25,061117,,,A*4D $GNVTG,44.25,T,,M,0.00,N,0.00,K,A*14 $GNZDA,114955.000,06,11,2017,00,00*47 $GPTXT,01,01,01,ANTENNA OK*35 ``` ## 二、需求介绍 得到GPS原始坐标数据之后,想通过百度地图API接口直接显示实际定位。 国际经纬度坐标标准为WGS-84,国内必须至少使用国测局制定的GCJ- 02,对地理位置进行首次加密。 百度坐标在此基础上,进行了BD-09二次加密措施,更加保护了个人隐私。 百度对外接口的坐标系并不是GPS采集的真实经 纬度,需要通过坐标转换接口进行转换。 ## 三、C语言代码 下面代码在QtCreator里编写,可以将代码移植到任何支持C语言的环境中编译运行。 ```cpp #include #include "QString" #include extern "C" { #include "math.h" } class GPS_Data { public: double lat; //纬度 double lng; //经度 QString GPS_Data; }; class GPS_Data gps_data; void GPS_ReadUasrtData(); #define M_PI 3.14159265358979324 double a = 6378245.0; double ee = 0.00669342162296594323; double x_pi = M_PI * 3000.0 / 180.0; double wgs2gcj_lat(double x, double y) { double ret1 = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x)); ret1 += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0; ret1 += (20.0 * sin(y * M_PI) + 40.0 * sin(y / 3.0 * M_PI)) * 2.0 / 3.0; ret1 += (160.0 * sin(y / 12.0 * M_PI) + 320 * sin(y * M_PI / 30.0)) * 2.0 / 3.0; return ret1; } double wgs2gcj_lng(double x, double y) { double ret2 = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x)); ret2 += (20.0 *sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0; ret2 += (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0; ret2 += (150.0 *sin(x / 12.0 * M_PI) + 300.0 * sin(x / 30.0 * M_PI)) * 2.0 / 3.0; return ret2; } void wgs2gcj(double *lat,double *lng) { double dLat = wgs2gcj_lat(*lng - 105.0, *lat - 35.0); double dLon = wgs2gcj_lng(*lng - 105.0, *lat - 35.0); double radLat = *lat / 180.0 * M_PI; double magic = sin(radLat); magic = 1 - ee * magic * magic; double sqrtMagic = sqrt(magic); dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * M_PI); dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * M_PI); *lat = *lat + dLat; *lng = *lng + dLon; } void gcj2bd(double *lat,double *lng) { double x = *lng, y = *lat; double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi); double theta = atan2(y, x) + 0.000003 * cos(x * x_pi); *lng = z * cos(theta) + 0.0065; *lat = z * sin(theta) + 0.006; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); GPS_ReadUasrtData(); //得到GPS原始坐标 double lat=gps_data.lat; double lng=gps_data.lng; //坐标转换 wgs2gcj(&lat,&lng); gcj2bd(&lat,&lng); //得到百度地图的坐标,可以直接在百度地图上显示 qDebug()"纬度: ""经度: "*GPS的原始数据帧*/ gps_data.GPS_Data="$GNGGA,114955.000,2842.4158,N,11549.5439,E,1,05,3.8,54.8,M,0.0,M,,*4F" "$GNGLL,2842.4158,N,11549.5439,E,114955.000,A,A*4D" "$GPGSA,A,3,10,31,18,,,,,,,,,,5.7,3.8,4.2*37" "$BDGSA,A,3,07,10,,,,,,,,,,,5.7,3.8,4.2*2A" " $GPGSV,3,1,10,10,49,184,42,12,16,039,,14,54,341,,18,22,165,23*7B" "$GPGSV,3,2,10,22,11,318,,25,51,055,,26,24,205,,29,13,110,*7C" "$GPGSV,3,3,10,31,50,287,36,32,66,018,*7F" "$BDGSV,1,1,04,03,,,07,05,,,29,07,79,246,33,10,52,232,19*62" "$GNRMC,114955.000,A,2842.4158,N,11549.5439,E,0.00,44.25,061117,,,A*4D" "$GNVTG,44.25,T,,M,0.00,N,0.00,K,A*14" "$GNZDA,114955.000,06,11,2017,00,00*47" "$GPTXT,01,01,01,ANTENNA OK*35"; /*解析GPS模块的数据*/ //QString lat; //纬度 //QString lng; //经度 if(gps_data.GPS_Data.size()>200) { int index=gps_data.GPS_Data.indexOf("$GNGGA"); if(index>=0) { QString text=gps_data.GPS_Data.mid(index); if(text.size()>60) { QString lat=text.section(',',2,2); QString lng=text.section(',',4,4); if(lat.isEmpty()==false && lng.isEmpty()==false) { unsigned int int_data; double s_Longitude,s_latitude; //转换纬度 s_latitude=lat.toDouble(); s_latitude=s_latitude/100; int_data=s_latitude;//得到纬度整数部分 s_latitude=s_latitude-int_data;//得到纬度小数部分 s_latitude=(s_latitude)*100; gps_data.lat=int_data+(s_latitude/60.0); //得到转换后的值 //转换经度 s_Longitude=lng.toDouble(); s_Longitude=s_Longitude/100; int_data=s_Longitude;//得到经度整数部分 s_Longitude=s_Longitude-int_data; //得到经度小数部分 s_Longitude=s_Longitude*100; //gai guo le gps_data.lng=int_data+(s_Longitude/60.0); } } } gps_data.GPS_Data.clear(); } } ```
  • [技术干货] 用c语言组合排列实现0-10的奇数问题——原创
    如何实现0-10所能组成的奇数个数 和第10位奇数是多少以及奇数的总个数是多少?程序分析:我们可以从问题中不难得出 这个问题属于排列组合的问题 比如设这个数为:a1a2a3a4a5a6a7a8a9a10,a1-a10标识这个数的某位的数值 如果最后一个数字为奇数则得出此数字一定是奇数  不管几位是什么数字 如果最后一位是偶数 那么此数字一定是偶数a1-a10可以取0-9这十个数字 首位数字不为0的个数。从该数为1位数时到该数为10位数时开始统计奇数个数是多少比如:1:当只有一位数时也就是该数的最后一位为个数  奇数的个数为1 3 5 7 9 一共5个奇数2:当该数为俩位时 奇数个数为5*9=453:当该数字为三位数字时候 数字个数为:5*10*9=1504:当该数字为8位数时个数就是5*10*10*10*10*10*10*9=45000000(依次为最后一位到第一位)以此类推求出到8位数的个数是多少思路:首先我们在main函数里定义一个数组指针的指针和int型的证书argc定义long型初始化为5的表示只有一位数字的奇数个数为5 intj循环从2开始 到10结束If条件判断当函数j<=2时为1-9的数如果j小于等于2则输出s累乘9的结果else是大于2的情况累乘10的结果每循环一次输出一次 最后跳出循环printf输出奇数的个数为从当前j-1指针前移得出的累乘结果 并输出奇数个数为sum最后打印出实验总结:本次代码实验的整体思路是排列组合的问题 推导出集中的可能性值从而输出相应的结果问题每次实验前都可以先画一个思维导图来判断程序的问题和走向代码:#include<stdio.h>int main(int agrc, char*agrv[]){    long sum = 5, s = 5;    int j;    for (j = 2; j <= 10; j++)    {            printf("%d位数为奇数的个数%ld\n", j-1, s);        if (j <= 2)            s *= 9;        else            s *= 10;        sum += s;        }    printf("%d位数为奇数的个数%ld\n", j-1, s);printf("奇数的总个数为:%ld\n", sum);return 0;}结果截图:
  • [技术干货] 初识C语言(中篇)[转载]
    10. 操作符算数操作符+        -        *        /        %算数操作符中的+ - *和平常运算方法相似,我们在这边重点介绍 / 和 %运算符/ 操作符为了让我们的讲解更加具有针对性,我们分正数和负数两部分进行讲解:/ 操作符对正数#include<stdio.h>int main(){    int a = 7 / 2;    printf("%d\n", a);//3}分析:对于上方代码当我们用平时的逻辑,那么这个答案应该是3.5,可在程序中编译运行,结果是 3,我们不禁产生疑惑是不是因为你打印的结果是整形的缘故,其实本身结果还是3.5呢?让我们用代码验证:#include<stdio.h>int main(){    float f = 7 / 2;    printf("%f\n",f);//3.000000}可当我们再次进行编译运行时,结果是3.000000,因此我们可以得出结论:除号两端的操作数如果都是整数,执行的是整数除法,至少有一个操作数是浮点数,执行的才是浮点数的除法!!!对于正数是这样进行运算的,那么负数呢?/ 操作符对负数#include<stdio.h>int main(){    int i = -2.9;    int j = 2.9;    printf("%d\n",j);//-2    printf("%d\n",j);//2    return 0;}对于负数运算,我们打印 j 的值,结果为 -2 ,和正数运算规律相似,我们可以看出它们运算都是向0取整的,那么如何理解呢?对于取整,当正负数进行运算时,并不会出现四舍五入的情况,无论正数负数都是向0方向取整!(-2.9向零取整得到2)如下图所示:小科普在C语言中,是不是只有一种取整方式?如果有它们分别是怎么实现的?接下来我们来一一了解:trunc - 取整函数#include<math.h>int main(){     printf("%d\n",(int)trunc(-2.9));//-2      printf("%d\n",(int)trunc(2.9));//2  }Tips:由于trunc函数默认接收的返回值为%f,但是我们这边想输出的是整数所以强制类型转化成int类型。floor - 地板取整#include<stdio.h>#include<math.h>int main(){    printf("%.lf\n",floor(-2.9));//-3    printf("%.lf\n",floor(-2.1));//-3    printf("%.lf\n",floor(2.9));//2    printf("%.lf\n",floor(2.1));//2}因为其取整方式和函数名,我们称它为地板取整,什么意思呢,我们编译结果发现负数-2.9的值变成了-3,对于正数2.9的值变成了2。我们可以观察到无论是正数还是负数,在用floor函数进行取整的时候都是变小,我们观察其本质可以总结为-∞取整,如下图所示:ceil - +∞取整#include<stdio.h>int main(){    printf("%.lf\n",ceil(-2.9));//-2    printf("%.lf\n",ceil(-2.1));//-2    printf("%.lf\n",ceil(2.9));//3    printf("%.lf\n",ceil(2.1));//3}当我们编译运行程序,可以观察到负数-2.9变为-2,-2.1变为-2,正数2.9变为3,2.1变为3。我们可以观察到ceil函数的取整,结果都会在原来的基础上变大一个整数位,我们总结出该取整方式为+∞取整,如下图所示:round - 四舍五入取整是的你没有看错,与我们平常思维方式相同的四舍五入取整来了,上代码:#include<math.h>int main(){    printf("%.lf\n",round(-2.9));//-3    printf("%.lf\n",round(-2.1));//-2    printf("%.lf\n",round(2.9));//3    printf("%.lf\n",round(2.1));//2}对于这种取整方式,没什么好说的,就是按照我们平时的思路来,例如当值2.9时,值为3,当值为2.1时,值为2。在当前情况下,凡取整的值大于等于2.5,得出的结果都会变为3。% 操作符概念:如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ r < d。其中,q 被称为商,r 被称为余数。对于此部分我们依旧是从正数和负数两方面进行剖析:% 操作符对正数#include<stdio.h>int main(){    int a = 10;    int d = 3;    printf("%d\n",a%d);//1    //因为:a=10,d=3,q=3,r=1 0<=r<d(3)    //所以:a=q*d+3 -> 10=3*3+1    return 0;}-代码:基于概念,我们可以理解当前代码的意思(详情见注释)。但我们还有另一种理解方式:% 操作符,关注的是除法后的余数,例如代码中的10%3,我们也可以理解为商3余1,相对于概念,这种方式更加容易接受一些。Warning:操作符两端的操作数必须是整数!!!% 操作符对负数int main(){    int a = - 10;    int d= 3;    printf("%d\n",a%d);//-1} 很显然,结果不满足定义中的0<=r<d,故后来就有了一个修订版的定义:如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r| < |d|。其中,q 被称为商,r 被称为余数。根据定义,这里的结果也能解释了,即-10 = (-3)*3+(-1)。在C语言中,%本质为取余,余数的大小,本质取决于商q的,而商都取决于除法运算的取整规则。在对于正数的取余和取模在C语言中是相同的,我们发现对于向0取整和向-∞的取整在正数上是同向的,但是对于负数上就完全不同了,因此我们平时遇到的大多都是整数取模,并没有做太多解释。移位操作符>>        <<(以后讲解)位操作符&         ^(以后讲解)赋值操作符=        +=        -=        *=        /=        &=        ^=        |=        >>=由于赋值操作符便于理解,我们挑两个讲一下(详情见注释):int main(){    int a = 10;//创建变量,并初始化    a = 20;//赋值    a = a+5//等价于 a+=5    a = a-5//等价于 a-=5}单目操作符!                 逻辑反操作-                 负值+                正值&                取地址sizeof         操作数的类型长度(以字节为单位)~                对一个数的二进制按位取反--                前置、后置--++              前置、后置++*                 间接访问操作符(解引用操作符)(类型)         强制类型转换我们常见的操作符比如+都有两个操作数,表示为左操作数+右操作数,这种操作符叫做双目操作符。单目操作符其实就是只有一个操作数的。在这里,对于!,-,+等操作符都便于理解,而对于&和*我打算之后在指针部分讲解,这里我们着重讲一下--,和++操作符(详情见注释):#include<stdio.h>int main(){    int a = 10;    int b = ++a;//前置++,先++后使用    //a=a+1,b=a    printf("a=%d b=%d\n",a,b);//11 11    ---------------------------------    int a = 10;    int b = a++;//后置++,先使用,后++    //b=a,a=a+1    printf("a=%d b=%d\n",a,b);//11 10    ---------------------------------    int a = 10;    int b = --a;//前置--,先--后使用    //a=a-1,b=a    printf("a=%d b=%d\n",a,b);//9 9    ---------------------------------    int a = 10;    int b = a--;//后置--,先使用,后--    //b=a,a=a-1    printf("a=%d b=%d\n",a,b);//10 9}关系操作符>         >=         <        <=        !=(不相等)        ==(相等)前几个操作符,也就是对应的大于,大于等于,小于,小于等于等比较关系的操作符,这里我们讲一下后两个操作符。例1int main(){    int a = 3;    int b = 3;    if(a!=b)    {        printf("hehe\n");        }    if(a==b)    {        printf("haha\n");        }    //结果为hehe}运行结果如下: 观察代码,很简单,判断a和b是否相等,如果相等则输出haha,若不相等输出hehe,因为c此处a和b都等于3,所以打印结果为hehe。例2#include<stdio.h>int main(){    char arr1[] = "abcdef";    char arr2[] = "abcdef";    if(arr1==arr2)    {        printf("==\n");        }    else    {        printf("!=\n");        }}我们知道用==和!=可以判断是否相等,那么对于字符串呢?他们是否相等,当我们编译后,发现它们是不相等的!因为arr1和arr2是字符串首元素地址,开辟的空间不同,所以地址当然不同,如果要比较两个字符串的大小可以用strcmp函数,接下来看修改后的代码:#include<stdio.h>#include<string.h>int main(){    char arr1[] = "abcdef";    char arr2[] = "abcdef";    if(strcmp(arr1,arr2)==0)    {        printf("==\n");        }    else    {        printf("!=\n");        }}再次运行:对于strcmp这个函数使用时需要应用头文件#include<string.h>,如果返回的值为0,则两个字符串相等。 逻辑操作符&& - 逻辑与 - 并且        || - 逻辑或 - 或者int main(){    int a= 3;    int b = 0;    int c = a && b;    printf("%d\n",c);//真为1,假为0    if(a&&b)    {        printf("hehe\n");        }    if(a||b)    {        printf("haha\n);    }}运行得:分析:&&的返回值是这样规定的,如果两个数都为真,则值为1,反之则为0。对于本代码由于a&&b为0,故不打印hehe,||的值是有一个为真则为真,值为1,反之为0。这里由于a||b为真,所以打印haha。条件操作符exp1?exp2:exp3#include<stdio.h>int main(){    int a = 10;    int b = 0;    b = (a>5?3:-3);    printf("%d\n",b);//3    return 0;}分析:条件操作符使用时先判断表达式1,如果表达式1结果为真,则输出第一个值,否则为第二个值,改题中因为a>10,所以打印结果为3。当然,这段代码也可以用if        else语句来表示:#include<stdio.h>int main(){    int a = 10;    int b = 0;    if(a>5)    {        b=3;        }    else    {        b=-3;        }    printf("%d\n", b);}逗号表达式exp1, exp2, exp3, …expN#include<stdio.h>int main(){    int a=3;    int b=5;    int c=0;    int d= (a+=2,b=b-c+a,c=a+b);    //      a=5   b=10   c=5+10             printf("%d\n",d);}分析:对于逗号表达式其特点为从左往右依次计算,整个表达式的结果是最后一个表达式的结果,因此在代码中d的值也就是15。下标引用、函数调用和结构成员[]        ()        .        ->对于这类操作符我们讲解一下[ ]操作符:#include<stdio.h>int main(){    int arr[10] = {0};    arr[4]=5;    return 0;}分析:对于上述代码就是引用下标4,将arr[4]的位置上赋值5。和一些操作符相同,[ ]也有两个操作数,例如arr 和 4是两个操作数,对于这个代码写成4[arr]也可以,但是这种写法不仅让人难以理解,而且比较挫,我们可千万不要写出这样的代码!!!11. 常见关键字C 语言标准中规定了32个关键字,而在之后C99的标准下又增加了五个关键字,anduin这次对大家进行归纳的是C标准中的32个关键字,我们根据其特性,可以把它分为几类:  数据类型关键字(12个)char:声明字符型变量或函数short:声明短整型或函数Int :声明整型变量或函数long :声明长整型变量或函数signed:声明有符号类型变量或函数unsigned:声明无符号整形变量或函数foat:声明浮点型变量或函数double:声明双精度变量或函数struct:声明结构体变量或函数union:声明共用体(联合)数据类型enum:声明枚举类型void:声明函数无返回值或无参数,声明无类型指针控制语句关键字(12个)1.循环控制(5个)for:一种循环语句do:循环语句的循环体while :循环语句的循环条件break:跳出当前循环continue:结束当前循环,开始下一轮循环2.条件语句(3个)if:条件语句else:条件语句否定分支goto:无条件跳转语句3.开关语句(3个)switch:用于开关语句case:开关语句分支default:开关语句中的"其他"分支4.返回语句(1个)return:函数返回语句(可以带参数,也可以不带参数)5.存储类型关键字(5个)auto:声明自动变量,一般不使用extern:声明变量实在其他文件中声明register:声明寄存器变量static:声明静态变量typedef:用以给数据类型取别名(但是该关键字被分到存储关键字分类中,虽然看起来没什么相关性)注意:存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。6.其它关键字(3个)const:声明只读变量sizeof:计算数据类型长度volatile:说明变量在程序执行中可被隐含地改变这里anduin带大家有选择性的,讲解几个关键字,若没有讲到日后我们一一讲解:autoauto 从字面意思来看就是自动的意思 它也被成为自动变量局部变量都是自动创建,自动销毁的,所以局部变量都是auto修饰的。int main(){    auto int a = 10;//局部变量都是auto类型的,因此auto基本会被省略    return 0;}typedef本质本质:类型重命名使用方法#include<stdio.h>typedef unsignde int u_int;int main(){    u_int x = 0;     return 0;}分析:当我们在写代码时,一些代码类型很长,例如无符号整型unsigned int或者结构体,指针等,这时我们就可以用typedef对它进行类型重命名,本段代码中就是用了typedef来重命名unsigned int为u_int。作用对类型重命名的一种解决方案,让我们在面临冗长的类型命名方面上更加简便,可以对一些不太好理解的数据类型进行简化。register我们先想想,数据在计算机上可以存放在哪里呢?1.内存2.硬盘3.高速缓存4.寄存器存储金字塔寄存器存在的本质从硬件层面上,提高计算机的运算效率。当读取数据时,计算器先去寄存器中读取,如果没有读取到,再去高速缓存区中读取,最后才是内存,而且在cpu再读取寄存器中的数据时,内存->高数缓存区(cache),cache->寄存器,这个数据传递过程会持续进行,大大提高效率!register 修饰变量也就是相同的原理,尽量把所修饰变量,放入CPU寄存区中,从而达到提高效率的目的。#include<stdio.h>int main(){    register int a = 10;    return 0;}register 修饰什么变量register可不是什么变量都适合修饰的,要知道寄存器的价格是很昂贵的,register所修饰的变量也得"精挑细选"一番。局部变量(全局变量由于其特性会导致CPU寄存器被长时间占用)不会被写入的(写入就需要写回内存,register本身就是快速读取,后续还要读取检测的话,就与原目的背道而驰了)高频被读取的变量(俗话说把钱用在刀刃上,存入了寄存器,当然要经常使用的)如果要使用,不要大量使用,寄存器的数量是有限的!小科普register修饰的变量,不能取地址!!!#include<stdio.h>int main(){    register int a = 0;     printf("&a = %p\n", &a);    return 0;}编译运行: 分析:因为register的作用,变量a已经被放入寄存器中了,你怎么取地址呢?结语以上就是初识C语言中篇的内容,虽然只有两个专题,但是内容其实也是很多的,希望我的文章对你有帮助,如果大家觉得anduin写的还可以的话,请点赞+收藏哦!你们的肯定就是对我最大的鼓励!今天也是高考第一天,祝广大学子辛勤奋战在学海,汗水扬起成功帆!愿大家都可以金榜题名!————————————————版权声明:本文为CSDN博主「ExplorerAnduin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/m0_67867172/article/details/125163589
  • [技术干货] C语言程序设计全套知识点合集(物联网、单片机底层开发必备)
    一、前言在物联网系统开发中,离不开各种传感器、单片机的程序开发;一个简单的物联网系统构成应该是: 感知层 + 网络传输层 + 平台管理层 。具体而言:感知层:由各种各样的嵌入式设备构成。而单片机是嵌入式系统的硬件组成,是嵌入式系统软件运行的载体和必要条件;嵌入式系统在整个物联网框架下,更多的体现为感知层的角色,作为智能设备存在于整个物联网体系中,是物联网数据的采集来源。网络传输层:底层部分由嵌入式设备到平台的IoT网络构成,比较常见的网络主要有NB,Lora,Zigbee,GSM,LTE等。顶层部分是由网络到TCP/IP网络的转化。平台管理层:这部分主要完成不同数据传输协议的转化,实现数据的远程存储和设备远程管理。比如有的智能设备利用lwm2m协议传送数据,有的使用的是MQTT,还有的喜欢用coap封装数据。平台需要根据协议,完成上报数据的解析,设备的管理和命令的下发动作。从组成关系来讲,物联网由嵌入式设备构成,嵌入式设备中包含单片机。作为一名物联网工程师,如果主要是负责感知层的开发,那么C语言肯定是一项必须精通的语言。目前C语言还是单片机里的主流开发语言,这篇合集主要就是介绍C语言的知识点,从基本数据类型、变量、数组、指针、结构体顺序来介绍C语言的学习思路。二、C语言基础知识点2.1 C语言-基本数据类型与位运算https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190852这篇文章作为基础知识点,总结C语言的基本数据类型有哪些,浮点数的精度,整数变量的空间范围,变量定义语法,变量命名规则,浮点数打印格式,基本数据类型printf对应的打印、位运算的知识点。2.2 C语言-语句(if,for,while,switch,goto,return,break,continue)https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190854这篇文章作为C语言基础知识点,介绍C语言常用的几个语句的用法、规则、使用案例。介绍的语句如下: if..else 判断语句 for循环语句 while循环语句 do..while循环语句 switch 语句 goto 语句 return 语句 break 语句 continue 语句2.3 C语言-数组https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190858C语言的数组是一个同类型数据的集合,主要用来存储一堆同类型的数据。程序里怎么区分是数组?[ ] 这个括号是数组专用的符号. 定义数组、 访问数组数据都会用到。2.4 C语言-函数的定义、声明、传参https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190859C语言里函数是非常重要的知识点,一个完整的C语言程序就是由主函数和各个子函数组成的,主函数调用子函数完成各个逻辑功能。2.5 C语言-一维指针定义与使用https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190911指针在很多书本上都是当做重点来介绍,作为C语言的灵魂,项目里指针无处不在。比如: 指针作为函数形参的时候,可以间接修改源地址里的数据,也就相当于解决了函数return一次只能返回一个值的问题。指针在嵌入式、单片机里使用最直观,可以直接通过指针访问寄存器地址,对寄存器进行配置;计算机的CPU、外设硬件都是依靠地址操作的。2.6 C语言-内联函数、递归函数、指针函数https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190915这篇文章介绍C语言的内联函数、递归函数、函数指针、指针函数、局部地址、const关键字、extern关键字等知识点;这些知识点在实际项目开发中非常常用,非常重要。2.7 C语言-void类型作为万能指针类型https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=190917void类型在基本数据类型里是空类型,无类型;void类型常用来当做函数的返回值,函数形参声明,表示函数没有返回值,没有形参。void类型不能用来定义变量,因为它是空类型–可以理解为空类型。2.8 C语言-指针作为函数形参类型https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=191033C语言函数里最常用就是指针传参和返回地址,特别是字符串处理中,经常需要封装各种功能函数完成数据处理,并且C语言标准库里也提供了string.h 头文件,里面包含了很多字符串处理函数;这些函数的参数和返回值几乎都是指针类型。这篇文章就介绍如何使用指针作为函数参数、并且使用指针作为函数返回值。2.9 C语言-字符串处理https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=191034字符串在C语言里使用非常多,因为很多数据处理都是文本,也就是字符串,特别是设备交互、web网页交互返回的几乎都是文本数据。字符串本身属于字符数组、只不过和字符数组区别是,字符串结尾有’\0’。 字符串因为规定结尾有'\0',在计算长度、拷贝、查找、拼接操作都很方便。2.10 C语言-结构体与位域https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=191137C语言里的结构体是可以包含不同数据类型和相同数据类型的一个有序集合,属于构造类型,可以自己任意组合,并且结构体里也可以使用结构体类型作为成员。结构体在项目开发中使用非常多,无处不在,有了结构体类型就可以设计很多框架,模型,方便数据传输,存储等等。2.11 C语言-学生管理系统(结构体+数组实现)https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=191138前面文章里介绍了结构体类型,知道结构体类型里可以存放不同的数据类型,属于一个有序的集合。这篇文章就使用结构体知识点完成一个小练习,使用结构体+数组设计一个简单的学生管理系统,作为结构体知识点的巩固练习。功能如下:(1). 欢迎界面提示(2). 输入密码登录(3). 功能: 录入学生信息、按照学号排序、按照成绩排序、输出所有学生信息、输出指定学生信息(学号、姓名、成绩)、计算成绩平均值值输出打印、删除指定学生信息、增加新的学生信息。(4). 功能模块采用菜单方式选择2.2 C语言-预处理(#define、#if...)https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=191139在C语言程序里,出现的#开头的代码段都属于预处理。预处理:是在程序编译阶段就执行的代码段。比如: 包含头文件的的代码#include <stdio.h> #include <stdlib.h> #include <string.h>后续这篇帖子会持续更新,持续加入C语言后面的知识点,方便大家寻找对应的知识点。
  • [技术干货] C语言-预处理(#define、#if...)
    ## 1. 区分预处理代码 在C语言程序里,出现的#开头的代码段都属于预处理。 预处理:是在程序编译阶段就执行的代码段。 **比如:** 包含头文件的的代码 ```cpp #include #include #include ``` **下面列出C语言里常用的预处理代码段:** | 指令 | 描述 | | -------- | ------------------------------------------------------------ | | #define | 定义宏 | | #include | 包含一个源代码文件 | | #undef | 取消已定义的宏 | | #ifdef | 如果宏已经定义,则返回真 | | #ifndef | 如果宏没有定义,则返回真 | | #if | 如果给定条件为真,则编译下面代码 #if (a==12)\|\|(11==12) | | #else | #if 的替代方案 | | #elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 #if 1==2 #define ABC "123" #elif 2==2 #define ABC "456" #elif 3==3 #define ABC "789" #endif | | #endif | 结束一个 #if……#else 条件编译块 | | #error | 当遇到标准错误时,输出错误消息 | | #pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 | ## 2. #define 预处理指令: 宏定义和宏替换 ### 2.1 宏替换基本用法和衔接符号使用示例 ```cpp #include //C预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理 #define STR_DATA "123456" //宏替换用法 //替换的宏一般都是大写 //STR_DATA 称为替换标识符 #define INT_DATA 6666 //带参宏 #define PRINT_DATA(x) if(x){printf("为真.\n");} \ else {printf("为假.\n");} int main(int argc,char **argv) { printf("%s\n",STR_DATA); printf("%d\n",INT_DATA); PRINT_DATA(1); PRINT_DATA(0); return 0; } ``` ### 2.2 宏替换的高级用法:#(字符串常量替换符号) ```cpp #include //字符串常量替换 #define PRINT_DATA1(a,b) printf(#a"456"#b"\n"); #define PRINT_DATA2(a,b) printf("%s,%s\n",#a,#b); int main(int argc,char **argv) { PRINT_DATA1(123,"789"); PRINT_DATA2(123,"789"); return 0; } ``` ### 2.3 宏替换的高级用法:##(数据拼接符号) ```cpp #include //数据拼接符号 #define PRINT_DATA1(a,b) printf("%d\n",a##b); int main(int argc,char **argv) { PRINT_DATA1(1,4); PRINT_DATA1(100,400); return 0; } #include //数据拼接符号 #define PRINT_DATA(a) a##a int main(int argc,char **argv) { printf("%d\n",PRINT_DATA(6)); return 0; } ``` ### 2.4 宏定义用法 ```cpp #include //#define DEF_A #ifdef DEF_A #define STR_DATA "123" #else #define STR_DATA "456" #endif int main(int argc,char **argv) { printf("STR_DATA=%s\n",STR_DATA); return 0; } ``` ## 3. #ifndef、#ifdef 语句 ```cpp #include #define AA #ifndef AA #error "你没有定义AA标识符" #endif #ifdef A1 #define STR_DATA "123" #elif 12==12 #define STR_DATA "456" #elif 34==45 #define STR_DATA "789" #else #define STR_DATA "ABC" #endif int main(int argc,char **argv) { printf("%s\n",STR_DATA); return 0; } ``` ## 4. defined 预编译语句 ```cpp #if 0 #include //常规的常量替换 #define A1 "1234" #define A2 6666 /*带参宏*/ #define MAX(a,b) ((a)>(b)?(a):(b)) #define A3(a) if(a){printf("为真.\n");} \ else {printf("为假.\n");} //替换字符串常量 # #define PRINTF_1(a) printf("%s\n",#a); #define PRINTF_2(a,b) printf(#a"456"#b"\n"); //"123456789" //数据拼接符号 ## #define PRINTF_3(a,b) printf("%d\n",a##b); //1234 int main(int argc,char **argv) { printf("%s\n",A1); printf("%d\n",A2); printf("MAX=%d\n",MAX(12,77)); A3(1); PRINTF_1(1234); PRINTF_2(123,789); PRINTF_3(12,34); return 0; } #endif #if 0 #include /* #if 0 //为真的时候执行 int data=123; #else //为假的时候执行 int data=666; #endif */ #define B1 //定义一个标识符 B1 #undef B1 //取消标识符B1定义 #ifdef B1 //为真的时候执行 int data=123; #else //为假的时候执行 int data=666; #endif int main(int argc,char *argv[]) { printf("data=%d\n",data); return 0; } #endif #include #define A3 #if !defined(A1) && !defined(A2) && !defined(A3) #error "没有定义 A1或者A2或者A3" #endif int main(int argc,char *argv[]) { return 0; } ``` ## 5. #include 预编译语句 ```cpp #include #include "./include_file/app.h" //#include "/mnt/hgfs/linux-share-dir/linux_c/include_file/app.h" /* #include 语法: 和 "" #include 告诉编译器,从系统的目录下寻找stdio.h 这个文件。 #include "stdio.h" 告诉编译器,从当前用户目录下寻找stdio.h 这个文件,如果找不到再从系统的目录下寻找。 #include "./include_file/app.h" 告诉编译器,从./include_file目录下寻找头文件 #include "include_file/app.h" #include "/mnt/hgfs/linux-share-dir/linux_c/include_file/app.h" */ int main(int argc,char *argv[]) { printf("%s\n",A1); return 0; } ``` ## 6. typedef 关键字 typedef 给类型取别名。 比如: ```cpp #include void func1(void); void func2(void); #if 0 //宏替换 #define u8 unsigned char #define u16 unsigned short #define u32 unsigned int #endif #if 1 //声明一种新类型 typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; //声明新数据类型: 函数指针类型: 类型名称 func_p typedef void (*func_p)(void); //定义一个存放函数地址的数组 func_p func_addr[10]={func1,func2}; //声明新数据类型: 类型abc typedef char abc[10]; #endif int main(int argc,char **argv) { u8 a; abc a1,a2; //通过数组调用函数 func_addr[0](); func_addr[1](); printf("%d\n",sizeof(a1)); printf("%d\n",sizeof(a2)); return 0; } void func1(void) { printf("123\n"); } void func2(void) { printf("456\n"); } ```
  • [技术干货] C语言-学生管理系统(结构体+数组实现)
    ## 1. 学生管理系统功能介绍 前面文章里介绍了结构体类型,知道结构体类型里可以存放不同的数据类型,属于一个有序的集合。 这篇文章就使用结构体知识点完成一个小练习,使用结构体+数组设计一个简单的学生管理系统,作为结构体知识点的巩固练习。 **功能如下:** (1). 欢迎界面提示 (2). 输入密码登录 (3). 功能: 录入学生信息、按照学号排序、按照成绩排序、输出所有学生信息、输出指定学生信息(学号、姓名、成绩)、计算成绩平均值值输出打印、删除指定学生信息、增加新的学生信息。 (4). 功能模块采用菜单方式选择 ## 2. 框架设计 ```cpp #include #include void Display_info(void); char buff_user[50]; char buff_password[50]; int main() { int a,i; printf("------欢迎使用xxxx学生管理系统------\n"); for(i=1;i=3;i++) { printf("请输入用户名:"); scanf("%s",buff_user); printf("请输入密码:"); system("stty -echo"); //系统函数---调用系统的命令 关闭回显 scanf("%s",buff_password); printf("\n"); system("stty echo"); //系统函数---调用系统的命令 开回显 //比较密码是否正确 if(strcmp(buff_user,"wbyq")!=0 || strcmp(buff_password,"123456")!=0) { printf("密码或者用户错误.请重新输入,剩余次数:%d\n",3-i); } else break; } //比较密码是否正确 if(strcmp(buff_user,"wbyq")!=0 || strcmp(buff_password,"123456")!=0) { printf("系统安全退出.\n"); return 0; //exit(); //终止进程 } /* #include void exit(int status); 比如: exit(0); */ while(1) { Display_info(); scanf("%d",&a); switch (a) { case 1: printf("选择录取学生信息\n"); break; case 2: printf("输出学生信息\n"); break; //............ default: break; } } return 0; } //显示提示信息 void Display_info(void) { printf("------欢迎使用xxxx学生管理系统------\n"); printf("1. 录取学生信息\n"); printf("2. 输出学生信息\n"); printf("3. 打印成绩平均值\n"); printf("4. 增加学生信息\n"); printf("5. 按照成绩排序输出学生信息.\n"); printf("6. 删除指定学生信息\n"); printf("请选择功能:\n"); } ``` ## 3. 完整代码 ```cpp #include #include struct STU { char name[100]; //姓名 int age; //年龄 char number[100]; //学号 float grade;//成绩 }; //全局变量定义区域 char buff_user[50]; char buff_password[50]; struct STU stu_info[100]; int stu_cnt=0; //存放学生总人数 //函数声明区域 void Display_info(void); int SystemLogin(void); void add_info(struct STU *stu,int flag); void grade_sort(struct STU *stu); void del_info(struct STU *stu); void print_info(struct STU *stu); int main() { int number; //1. 登录系统 if(SystemLogin()) { printf("系统安全退出.\n"); exit(0); } //2.执行功能模块 while(1) { Display_info(); scanf("%d",&number); switch (number) { case 1: //录入学生信息 add_info(stu_info,1); break; case 2: //输出学生信息 print_info(stu_info); break; case 3: //打印成绩平均值 break; case 4: //增加学生信息 add_info(stu_info,0); break; case 5: //按照成绩排序输出学生信息 grade_sort(stu_info); break; case 6: //删除指定学生信息 del_info(stu_info); break; case 7: //退出系统 printf("-----系统安全退出------\n"); exit(0); //退出当前进程 default: break; } } return 0; } //显示提示信息 void Display_info(void) { printf("\n\n"); printf("------欢迎使用xxxx学生管理系统------\n"); printf("1. 录入学生信息(按照要求录入信息)\n"); printf("2. 输出学生信息\n"); printf("3. 打印成绩平均值\n"); printf("4. 增加学生信息\n"); printf("5. 按照成绩排序输出学生信息.\n"); printf("6. 删除指定学生信息\n"); printf("7. 退出系统\n"); printf("请选择功能:"); } /* 函数功能: 系统登录 返回值: 0表示成功 1表示失败 */ int SystemLogin(void) { printf("------欢迎使用xxxx学生管理系统------\n"); int i; for(i=1;i=3;i++) { printf("请输入用户名:"); scanf("%s",buff_user); printf("请输入密码:"); system("stty -echo"); //系统函数---调用系统的命令 关闭回显 scanf("%s",buff_password); printf("\n"); system("stty echo"); //系统函数---调用系统的命令 开回显 //比较密码是否正确 if(strcmp(buff_user,"wbyq")!=0 || strcmp(buff_password,"123456")!=0) { printf("密码或者用户错误.请重新输入,剩余次数:%d\n",3-i); } else break; } //比较密码是否正确 if(strcmp(buff_user,"wbyq")!=0 || strcmp(buff_password,"123456")!=0) { return 1; } return 0; } /* 函数功能: 录入学生信息 int flag :1表示连续增加学生信息 0表示增加单个学生信息 */ void add_info(struct STU *stu,int flag) { int i; int tmp; int addr=stu_cnt; //学生总人数 if(flag) { for(i=addr;isizeof(stu_info[0]);i++) { printf("请输入学生姓名:"); scanf("%s",stu[i].name); printf("请输入学生年龄:"); scanf("%d",&stu[i].age); printf("请输入学号:"); scanf("%s",stu[i].number); printf("请输入成绩:"); scanf("%f",&stu[i].grade); stu_cnt++; //记录总人数 printf("是否继续录入? 0表示继续 1表示退出"); scanf("%d",&tmp); printf("\n\n"); if(tmp)break; } } else { printf("请输入学生姓名:"); scanf("%s",stu[addr].name); printf("请输入学生年龄:"); scanf("%d",&stu[addr].age); printf("请输入学号:"); scanf("%s",stu[addr].number); printf("请输入成绩:"); scanf("%f",&stu[addr].grade); stu_cnt++; //记录总人数 } } //输出学生信息 void print_info(struct STU *stu) { int i; printf("--------------------------------------------------\n"); for(i=0;i} //按照成绩排序输出学生信息 void grade_sort(struct STU *stu) { int i,j; struct STU tmp; for(i=0;i stu[j+1]=tmp; } } } } //1234567890 //删除指定学生信息 void del_info(struct STU *stu) { char number[100]; int i,j; printf("请输入要删除学生的学号:"); scanf("%s",number); printf("--------------------------------------------------\n"); for(i=0;i { printf("成功删除以下学生信息:"); printf("学生姓名:%s\n",stu[i].name); printf("学生年龄:%d\n",stu[i].age); printf("学号:%s\n",stu[i].number); printf("成绩:%f\n",stu[i].grade); for(j=i;j } stu_cnt--; //减去总数量 break; } } if(i==stu_cnt+1) { printf("没有找到该学号.\n"); } printf("--------------------------------------------------\n"); } ```
  • [技术干货] C语言-结构体与位域
    ## 1. 结构体介绍 C语言里的结构体是可以包含不同数据类型和相同数据类型的一个有序集合,属于构造类型,可以自己任意组合,并且结构体里也可以使用结构体类型作为成员。 结构体在项目开发中使用非常多,无处不在,有了结构体类型就可以设计很多框架,模型,方便数据传输,存储等等。 **结构体定义语法** ```cpp struct 结构体名称 { 数据类型1 成员名1; 数据类型2 成员名2; 数据类型3 成员名3; ..... }; 结构体的名称命名规则: 和普通变量命名规则一样—遵循C语言变量命名标准。 ‘A’~‘Z’ ‘a’~ ’z’ ‘0’~’9’ _ ``` **示例代码:** ```cpp struct app { int age; //年龄 char name[100]; //姓名 int number; //学号 }; 上面这一块代码表示定义(声明)一个新的结构体类型。 数据类型名称:struct app ``` ## 2. 如何使用结构体定义变量? 结构体定义变量有3种形式: ```cpp #include //第一种形式:在定义结构体类型的时候同时定义变量 struct app1 { int age; //年龄 char name[100]; //姓名 int number; //学号 }a1,a2,a3; //a1 a2 a3就是定义的结构体变量 //第二种形式 struct app2 { int age; //年龄 char name[100]; //姓名 int number; //学号 }; //第三种形式: 匿名方式定义结构体 struct { int age; //年龄 char name[100]; //姓名 int number; //学号 }c1,c2,c3; //c1 c2 c3就是定义的结构体变量 int main() { //使用结构体类型定义变量 struct app2 b1; struct app2 b2; struct app2 b3; return 0; } ``` ## 3. 结构体的赋值 **结构体变量的赋值语法:** ```cpp 结构体变量名.成员名=xxx; ``` **结构体初始化赋值说明:** 结构体只能在(定义结构体变量的时候)初始化的时候支持整体赋值,之后就只能按照成员单个赋值。 注意:*结构体变量之间支持直接赋值。* ```cpp #include #include //第一种形式:在定义结构体类型的时候同时定义变量 struct app1 { int age; //年龄 char name[100]; //姓名 int number; //学号 }a1={23,"小白",5678},a2,a3={12,"小明",1234}; //a1 a2 a3就是定义的结构体变量 //第二种形式 struct app2 { int age; //年龄 char name[100]; //姓名 int number; //学号 }; int main() { //使用结构体类型定义变量 struct app2 b1={15,"小李",6878}; struct app2 b2; struct app2 b3; //单个修改结构体成员变量的值 b1.age=18; //b1.name="555"; strcpy(b1.name,"小丽"); printf("b1:%d\n",b1.age); printf("b1:%s\n",b1.name); printf("a1:%d\n",a1.age); printf("a1:%s\n",a1.name); //结构体变量之间支持直接赋值 (要保证变量的类型要一致)。 //int a=100; //int b; //b=a; b2=b1; //将b1结构体变量赋值给b2结构体变量 printf("b2:%d\n",b2.age); printf("b2:%s\n",b2.name); return 0; } ``` ## 4. 结构体指针定义与使用 示例代码: ```cpp #include #include #include struct app { int age; //年龄 char name[100]; //姓名 int number; //学号 }; int main() { struct app a1; //定义一个结构体变量 struct app *p1; //定义一个结构体指针 struct app *p2; //定义一个结构体指针 p1=&a1; //地址赋值 p1指向a1的空间 //申请堆空间 p2=malloc(sizeof(struct app)); //通过指针访问成员 p1->age=20; strcpy(p1->name,"小红"); p1->number=1234; //输出数据 printf("姓名:%s\n",p1->name); printf("学号:%d\n",p1->number); printf("年龄:%d\n",p1->age); //通过指针访问成员 p2->age=13; strcpy(p2->name,"小李"); p2->number=5678; //输出数据 printf("姓名:%s\n",p2->name); printf("学号:%d\n",p2->number); printf("年龄:%d\n",p2->age); //释放空间 free(p2); return 0; } ``` ## 5. 结构体数组定义与使用 **示例代码:** ```cpp #include #include #include struct app { int age; //年龄 char name[100]; //姓名 int number; //学号 }; int main() { //定义一个结构体数组 struct app buff[10]; //一次定义了10个结构体变量 struct app *p=buff; //定义一个结构体指针 //访问成员 buff[0].age=10; strcpy(buff[0].name,"小米"); buff[0].number=1234; //打印数据 printf("姓名:%s\n",buff[0].name); printf("学号:%d\n",buff[0].number); printf("年龄:%d\n",buff[0].age); printf("姓名:%s\n",p[0].name); printf("学号:%d\n",p[0].number); printf("年龄:%d\n",p[0].age); return 0; } ``` ## 6. 结构体当做函数的形参和返回值 示例代码: ```cpp #include #include #include struct app { int age; //年龄 char name[100]; //姓名 int number; //学号 }; struct app *func_stu(struct app *p); int main() { //定义一个结构体数组 struct app buff[10]; //一次定义了10个结构体变量 //调用函数 func_stu(&buff[0]); //打印数据 printf("姓名:%s\n",buff[0].name); printf("学号:%d\n",buff[0].number); printf("年龄:%d\n",buff[0].age); return 0; } //定义函数 struct app *func_stu(struct app *p) { //访问成员 p->age=10; strcpy(p->name,"小米"); p->number=1234; return p; } ``` ## 7. typedef关键字在结构体里使用方法 ```cpp #include #include #include typedef struct app { int age; //年龄 char name[100]; //姓名 int number; //学号 }STU; //STU叫结构体类型,相当于struct app的别名。 struct app == STU STU *func_stu(STU *p); int main() { //定义一个结构体数组 STU buff[10]; //一次定义了10个结构体变量 //调用函数 func_stu(&buff[0]); //打印数据 printf("姓名:%s\n",buff[0].name); printf("学号:%d\n",buff[0].number); printf("年龄:%d\n",buff[0].age); return 0; } //定义函数 STU *func_stu(STU *p) { //访问成员 p->age=10; strcpy(p->name,"小米"); p->number=1234; return p; } ``` ## 8. 结构体位域 位域用的不多,但是也有地方使用,主要是节省空间。 ```cpp #include struct app { unsigned int a:12; //定义位域,指定位宽 12的单位是位 unsigned int b:16; unsigned char c:1; //定义空间存储1位的数据。 1和0 unsigned int :5; //位域的名称可以省略掉,为了空间内存对齐而存在的 }; /* 1. 位域的大小不能超出本来数据类型大小。 2. 位域的名称可以省略掉,为了空间内存对齐而存在的 3. 位域的成员无法取地址操作 */ int main() { struct app data; //data.c=2; 错误 超出范围 只能存放0~1 //data.b=65535; 错误 超出范围 只能存放0~65535 // data.a=4096; 错误 超出范围 只能存放0~65535 //printf("%d\n",data.c); // printf("%d\n",data.b); //printf("%d\n",data.a); //printf("%d\n",&data.a); //错误 位域的成员无法取地址操作 data.c=1; data.b=555; //只能存放0~65535 data.a=888; //只能存放0~65535 printf("%d\n",data.c); printf("%d\n",data.b); printf("%d\n",data.a); return 0; } ``` ## 9. 结构体的内存对齐 ### 9.1 示例1: 计算结构体内存对齐 ```cpp #include struct app { int a1; char a2; char a3; char a4; char a5; char a6; char a7; }; /* 目前32位系统里,使用的是gcc编译器。 开空间的对齐原理:以结构体里出现的最大数据类型的倍数开空间,最大是4个字节。 */ int main() { struct app data; printf("空间大小:%d 字节\n",sizeof(struct app)); //8 return 0; } //func("1",1123,45,"45",123.45,'A'); void func(char *p,...) { } ``` ### 9.2 示例2: 计算结构体内存对齐 ```cpp #include #if 0 struct app { char a1; //1 short a2; //2 }; 最终占用空间大小4个字节 #endif #if 0 struct app { int a1[10]; //4*10=40 char a2[10]; //12 int a3; //4 float a4; //4 char a5; //4 }; //最终占用空间大小64个字节 #endif #if 1 struct app { int a1; double a2; }; //最终占用空间大小64个字节 #endif /* 目前32位系统里,使用的是gcc编译器。 开空间的对齐原理:以结构体里出现的最大数据类型的倍数开空间,最大是4个字节。 */ int main() { struct app data; printf("空间大小:%d 字节\n",sizeof(struct app)); //8 return 0; } //func("1",1123,45,"45",123.45,'A'); void func(char *p,...) { } ``` ### 9.3 输出结构体变量成员的地址,查看空间对齐情况 ```cpp #include struct app { int a1[10]; //4*10=40 char a2[10]; //12 int a3; //4 float a4; //4 char a5; //4 }; //最终占用空间大小64个字节 int main() { struct app data; //输出地址 查看空间对齐原理 printf("%#x\n",data.a1); printf("%#x\n",data.a2); printf("%#x\n",&data.a3); printf("%#x\n",&data.a4); printf("%#x\n",&data.a5); return 0; } ``` ### 9.4 对齐结构体进行强制对齐 ```cpp #include #pragma pack(1) //1 2 4 8 struct app { int a1[10]; //4*10=40 char a2[10]; //1*10=10 int a3; //4 float a4; //4 char a5; //1 }; int main() { struct app data; //输出地址 查看空间对齐原理 printf("%#x\n",data.a1); printf("%#x\n",data.a2); printf("%#x\n",&data.a3); printf("%#x\n",&data.a4); printf("%#x\n",&data.a5); printf("%d\n",sizeof(struct app)); return 0; } ```