这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » 【学习会】一起学习C语言之C了又C。。。

共8条 1/1 1 跳转至

【学习会】一起学习C语言之C了又C。。。

菜鸟
2018-05-07 14:14:57     打赏

   一入C门深似海,DSP这东西和C还真是密不可分。这次借着学习会的机会,一边通过阅读《明解C语言 入门篇(第三版)》,复习C语言的语法知识,重温C的基础理论,一边和大家分享复习当中发现的自身的语法疏漏(也就是冷知识)。各位大牛在这里就当走马观花复习复习,各位童鞋在这里就和我一起复习一下这些知识点。除了将这本书的冷知识部分摘抄了出来,笔记中还记录了这些冷知识的相关解读和一些关联的资料可以方便童鞋们进一步了解和学习。

  读书笔记中,目录前面的1.1、2.1是指对应书籍中的章节,括号中红字标出的是对应书中的页码,读书笔记的主要内容是对书中冷知识的摘录以及补充,欢迎大家一起完善讨论,最后会把word版本的发上来,给想要保存的童鞋留个记录,方便学习

     

    

这是个目录:

1.1 全角符号VS半角符号  2.1 正负数除法取余的运算结果

2.2 scanf和printf对于float和double的处理  %转换说明符

3.1 运算符优先级与短路求值  4.1 后置递增运算符和前置递增运算符

5.1 连续赋值表达式

6.2 & 6.3 多维数组下表传递注意 &  

      变量的作用域和存储期之全局变量VS静态全局变量

7.1 & 7.2  limits.h中的数据类型范围定义 & 负数移位&数据溢出

7.4 数据类型转换——有符号整数与无符号整数

8.1 宏的弊端 & 8-3 关于枚举

9-1 字符数组初始化数组名就是数组首地址

10. printf打印变量地址10.1 register修饰的对象取址

10.2 数组名在什么时候不被视为指向起始元素的指针

10.3 a[b]和b[a]一样么?

传说中的期中测试(new)




菜鸟
2018-05-14 23:50:58     打赏
2楼

1.1 显示计算结果(全角符号VS半角符号)

  P2首先应该注意的,C语言是区分大小写和全半角字符的!程序中诸如printf("%d", 15 + 37)中的引号等符号不可用全角输入,所以在码代码的时候输入法状态一定要时刻关注。本人因为使用qq输入法(这不算广告吧。。。),经常在输入注释的时候用中文,打代码再切回来,有的时候就会把分号打成中文的,大家引以为戒啊


2.1 运算(正负数除法的运算结果)

 P27C语言中进行除法运算的/运算符和%运算符的运算结果的符号是依赖于编译器的,当两个操作数都是正数时,除法和取余结果都是正数。而当两个操作数中至少有一个是负数时,计算结果就取决于编译器了。书中给出了几种可能的结果:



x / y

x % y

÷  x=-22, y=-5

4

-2

5

3

÷  x=-22, y=5

-4

-2

-5

3

÷  x=22, y=-5

-4

2

-5

-3

 我试了试,VS2010的C/C++编译器给出的结果都是偏上方的结果,欢迎补充测试结果

2.2 数据类型(scanf和printf对于float和double的处理)

 P35在用scanf函数赋值的时候,如果要获得double类型的数据,则要使用格式字符串%lf。这里区分下scanfprintf在处理double类型数据的区别。


int类型

double类型

使用printf函数显示

printf("%d",   no)

printf("%f",   no)

使用scanf函数读取

scanf("%d",   &no)

scanf("%lf",   &no)

这可能是因为scanf的格式字符串"%f"是用于扫描float类型的数据的,测试发现用"%f"可以实现对float类型的赋值,如果用"%f"double赋值就会出现乱码。

#include <stdio.h>

int main(void)

{

    float num;

    scanf("%f", &num);

    printf("%f\n", num);

    return 0;

}

结果是这样的:

#include <stdio.h>

int main(void)

{

    double num;

    scanf("%f", &num);

    printf("%f\n", num);

    return 0;

}

结果是这样的:


 P41说到printfscanf就要说说格式化字符串。常用的格式化字符串的格式是%0a.bC%-a.bC,这里0为“0标志”,设定了“0标志”之后,如果数值的前面有空余位,就用0补齐位数,而如果省略类“0标志”就会用空白补齐位数;a为最小字段宽度,也就是至少要显示出的字符位数,不设定该位数或显示数值的实际位数超过它的时候,会根据数值显示出必要的位数(如果设定类“-”,数据会左对齐显示,未设定则会右对齐显示);b用于指定显示的最小位数,如果不指定,则整数的时候默认为1,浮点数的时候默认为6C转换说明符下面给出转换说明符列表

转换说明符

用处

转换说明符

用处

%a

浮点数、十六进制数和p计数法

%c

字符

%d

有符号十进制数

%f

浮点数(包括floatdouble

%e(%E)

浮点数指数输出(e记数法)

%g(%G)

浮点数不显示无意义的零

%i

有符号十进制整数(同%d

%u

无符号十进制整数

%o

八进制整数

%x%X

十六进制整数

%p

指针

%s

字符串

%%

%符号





菜鸟
2018-05-15 09:45:45     打赏
3楼
3.1 if语句(运算符优先级与短路求值)

  P54这里涉及到了运算符的优先级问题,所以给出C语言中的所有运算符优先级列表。书中重点说了%符号的优先级比==运算符高。根据运算符表,等于==符号比按位与&、按位异或^、按位或|、逻辑与&&、逻辑或||等优先级高,所以在涉及到这些运算符的操作时一定要使用括号标明运算优先级。

123.jpg

        所谓的短路求值,就是指&&运算符在左操作数的判断结果为0时不对右操作数进行判断;||运算符在左操作数的判断结果不为0时不会对右操作数进行判断。像这样在仅根据左操作数的判断结果就可以知道逻辑表达式的判断结果的情况下,不会对右操作数进行判断。短路求值的特性一般会影响程序代码的执行,比如如果在被短路的语句中存在需要执行的自增自减等代码,而因为短路而无法执行,就会使程序的运行结果偏离预期,而这种错误是编译器无法检测出来的。


4.1 do语句(后置递增运算符和前置递增运算符)

  P85后置递增运算符a++,使a的值增加1,该表达式的值是增加前的值,后置递减运算符a--,使a的值减少1,该表达式的值是减小前的值。前置递增运算符++a,使a的值增加1,该表达式的值是增加后的值,前置递减运算符--a,使a的值减少1,该表达式的值是减小后的值。一般每用一次就要查一遍书。。。。。。


5.1 数组(连续赋值表达式)

        P134对于赋值表达式而言,可以允许连续赋值操作,即a = b = 0;是正确的,但这仅仅是对赋值而言,对带有初始值的变量声明并不适用,下面的语句是错误的:int a = b = 0;




菜鸟
2018-05-15 10:29:31     打赏
4楼

        第六章的笔记开始走起啦~大家走过路过,复习复习啊,就当查缺捡漏了


6.2 函数设计(多维数组下标传递注意

  P177在接收多维数组的函数中,可以省略相当于开头下标的n维的元素个数,但(n-1)维之下的元素个数必须是常量。这也就是说,在向函数中传递数组的时候,最高维可以不传递,但其余维度的下表范围值必须传递,而且是以常数的形式。

 

6.3 作用域和存储期(全局变量VS静态全局变量

  P180这里重点介绍了存储期的概念。函数中声明的变量并不是从程序开始到程序结束始终有效的,变量的生存期也就是寿命有两种,分别是自动存储期静态存储期。所谓的自动存储期就是一般意义上的变量,这些变量在程序执行到对象声明的时候就创建出了相应的对象,而执行到包含该声明的程序块的结尾该对象就会消失。而且如果在声明时不对该对象进行初始化,则该对象的值不定。所谓的静态存储期,就是通过在对象声明前加static关键字定义出来的对象,或者在函数外声明定义出来的对象,它们在程序开始执行的时候,具体地说是在main函数执行的准备阶段被创建出来,在程序结束时消失,如果在声明的时候不显式初始化,则会默认初始化为0

  说到静态变量,就不得不说到静态变量全局静态变量的差别。通过查阅资料总结出了下面的内容:全局变量分静态全局变量和非静态全局变量,静态全局变量就是利用static修饰的全局变量,这两个定义在只有一个源文件组成的工程中是没有区别的,而当工程有超过1个源文件组成时,在某个源文件中定义的非静态全局变量可以在其他源文件中通过extern关键字修饰进行声明并使用,而在某个源文件中定义的静态全局变量就不可以在其他源文件中被使用。总的来说,对于局部变量而言,静态与否决定了变量的生存期,而对于全局变量而言,静态与否决定了变量的使用范围。(这里给出了几个参考源,大家感兴趣的可以点进去看看啊https://blog.csdn.net/gjw198276/article/details/2010150 https://blog.csdn.net/tigerjibo/article/details/7425580 https://blog.csdn.net/u011425939/article/details/60465129 

       书中还介绍了auto关键字和register关键字修饰的自动存储期变量。现在一般不使用auto(用不用没区别),但registerDSP开发领域还是有一定用处的,主要的用途是帮助编译器识别DSP内部硬件寄存器。

  第六章结束啦,大家加油



菜鸟
2018-05-19 00:08:34     打赏
5楼
7-1 limits.h中的数据类型范围定义

       P198书中介绍了C语言编译器在<limits.h>头文件中以宏定义的形式定义了字符型以及其他所能表示的数值的最小值和最大值。这里贴出VS2017中编译器对应的limits.h部分内容:

 

#define CHAR_BIT      8         // number of bits in a char

#define SCHAR_MIN   (-128)      // minimum signed char value

#define SCHAR_MAX     127       // maximum signed char value

#define UCHAR_MAX     0xff      // maximum unsigned char value

#define MB_LEN_MAX    5             // max. # bytes in multibyte char

#define SHRT_MIN    (-32768)        // minimum (signed) short value

#define SHRT_MAX      32767         // maximum (signed) short value

#define USHRT_MAX     0xffff        // maximum unsigned short value

#define INT_MIN     (-2147483647 - 1) // minimum (signed) int value

#define INT_MAX       2147483647    // maximum (signed) int value

#define UINT_MAX      0xffffffff    // maximum unsigned int value

#define LONG_MIN    (-2147483647L - 1) // minimum (signed) long value

#define LONG_MAX      2147483647L   // maximum (signed) long value

#define ULONG_MAX     0xffffffffUL  // maximum unsigned long value

这里可以看到其中一些含有LU的数字常量,含有L的常量是长整型常量long,而Uunsigned无符号的意思。这里的L用于将数字以long类型进行存储,

 

7.2 负数移位&数据溢出

       当对负数进行移位运算时,根据编译器的不同有可能执行算数位移或逻辑位移。算数位移就是考虑正负数的位移,而逻辑位移就是将负数看做unsigned来实现位移,所以不建议对负数进行位移运算。(据说C#的编译器就针对负数位移给出了解决方案,引入了一个>>>的算数右移来实现对负数的位移操作,感兴趣的童鞋可以了解下)。

       这里还有一个常见的隐性错误,就是数据溢出,这常常出现在数组越界、变量赋值超范围等情况下。这些错误的变异提示随着编译器不同而不同,有些编译器是不会给出提示的(比如CCS的编译器就不会提示数组越界这种问题,现象就是程序运行过程中在不相关的代码处修改了某个数组或变量的数值),这需要小伙伴们注意下。



菜鸟
2018-05-22 22:37:46     打赏
6楼

7.4 数据类型转换——有符号整数与无符号整数

       (P230)将有符号整数转换为位数相同的或位数更多的无符号整数时,如果该有符号整数不为负数,则数值不会发生变化。否则,如果无符号整数的位数较大,则先将有符号整数提升为与无符号整数长度相同的有符号整数,然后再与无符号整数类型可表示的最大数加1后的值相加,将有符号整数转换为无符号整数。将整数类数据类型转换为位数更少的无符号整数类型时,除以比位数较少的数据类型可表示的最大无符号数大1的数,所得的非负余数就是转换后的值。将整数类数据类型转换为位数更少的有符号整数时,以及将无符号整数转换为位数相同的有符号整数时,如果不能正确表示转换后的值,则此时的操作由编译器决定。

8.1 宏的弊端

       (P238)函数式宏在使用上必须要小心谨慎,例如对于宏定义为

#define sqr(x) ((x) * (x))

  当使用上面的宏sqr(x++)时,展开后为((x++)*(x++)),即每次展开,x的值都会自增两次,这就是使用函数式宏的副作用。(说句题外话,今天看《Effective C++》,里面对C语言的宏给出的建议是能少用就少用,并建议用const修饰的全局变量代替宏常量定义,用inline修饰函数来代替函数式宏,可见这个宏真是弊端不小。。。)

8-3 关于枚举

       (P247)枚举因为个人不常用,所以接触的比较少。首先枚举名不是类型名,类型名为enum+枚举名,而且枚举值实际上对应着int值,多个枚举值可以对应同一个int值。

   这一章中后面还涉及到了重定向的相关知识,留到后面文件处理部分讨论输入输出流的时候再讨论吧,第八章K.O。



菜鸟
2018-05-27 03:03:22     打赏
7楼

9-1 字符数组初始化

P269对于字符数组,除了初始化赋值的时候,不能将数组的初始值或字符串直接赋予数组变量,在后面修改的时候只能采用依次赋值的方式。这里引用一下书后的练习,

#include "stdafx.h"

 

int main()

{

    char s[] = "ABC\0DEF";

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

    return 0;

}

这里输出的结果是ABC,而通过断点观察到s字符串实际上存储了全部的数据ABC\0DEF\0,这说明\0对于字符串的终结作用在字符串赋值的时候并没有体现出来,换句话说,手工输入的\0转义字符在字符串的存储中并没有任何作用,在数组初始化的时候仍旧会自动在字符串结尾加上\0作为结束。但当字符串输入到printf等格式化输入输出函数中时,printf函数会自动识别\0转义字符,从而将字符串在ABC后面直接截断,所以得到的输出只有ABC\0

9-1 数组名就是数组首地址

       P271书中说到了在实用scanf函数试图将键盘输入的数据存入到char name[48]中时,在给如目标地址时,不能使用&name而要使用name。这是因为对于char name[48]而言,这是一个char型数组。此时name代表的就是这个数组的第一个元素存储的地址,这和声明一个变量int a之后将&a传入scanf是一样的。(在这里,name也等价于&name[0])。

 

10 printf打印变量地址

       P289表示对象地址的转换说明符是%p,不过经过测试,发现%x也可以用于输出(十六进制)地址的转换说明符。因为&取址得到的实际上就是一个数字。

 

10.1 register修饰的对象取址

       P301对于使用register关键字声明的寄存器对象,不能通过&符号进行取址,因为寄存器通常位于CPU内,所以并不存在地址一说,所以出现&符号进行取址的程序时编译器会报错。

 

10.2 数组名在什么时候不被视为指向起始元素的指针

       这里mark一下,当sizeof(数组)时返回的是整个数组的长度而不是第一个元素占得大小;当作为取址符&的操作数出现时,&数组名不是指向起始元素的指针的指针,而是指向数组整体的指针。

10.3 a[b]b[a]一样么?

       呃,这个结果是一样的,各位可以试试,为什么我也不知道,只是偶然在一本书中看到过,但大家尽量还是好好写代码,毕竟这不是国际C语言混乱代码大赛(IOCCC, The International Obfuscated C Code Contest)。



菜鸟
2018-06-06 15:00:29     打赏
8楼

1. 请在控制台实现如下功能。

*

**

***

****

答:因为在控制台中,不存在半字符操作(最小显示单位就是字符,一个汉字由占据两个字符的位置),所以我并没有想出半字符缩进的实现方式。

程序:

#include "stdafx.h"

int main()

{

    printf("   *   \n");        //

    printf("  * *  \n");

    printf(" * * * \n");

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

    return 0;

}

结果截图:

2.效果与第一题结果相同,但是打印的行数由程序运行时确定。

例如:

程序运行时输入2

*

**

程序运行时输入3

*

**

***

答:

程序源码:

#include "stdafx.h"

#include <stdio.h>

void show_stars(int num);

 

int main()

{

    // 变量定义与初始化

    int line_num = 0;  

 

    // 打印输入提示语句

    printf("Please input the lines you wish to show and\n"\

        "press Enter to confirm:");

    // 接收行数变量

    scanf("%d", &line_num);

 

    // 调用打印星号函数

    show_stars(line_num);

 

    return 0;

}

 

void show_stars(int num)

{

    // 判定输入的行数是否为正数,不是则给出错误提示

    if (num <= 0){

        printf("Please input a valid integer that is bigger\n"\

            "than 0 and retry!");

    }

    else{

        // 循环中逐行打印星号

        for (int i = 0; i < num; i++) {

            // 对于第i行,该行的第一个星号之前需要键入空格

            for (int j = 0; j < num - 1 - i; j++) {

                printf(" ");

            }

            // 之后输入星号和空格的组合

            for (int j = 0; j < i; j++) {

                printf("* ");

            }

            // 最后补上每行最后一个星号以及换行符

            printf("*\n");

        }

    }

}

结果截图:

3.请实现2个函数完成BCD码到二进制格式的转换和二进制到BCD转换功能,并自己编写测试。

答:

程序源码:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

int Bin2BCD(char *str);

int BCD2Dec(char *str);

int main(void)

{

    int mode = 0;

    int num_length = 0;

    printf("Please Input the length you wish to convert:");

    scanf("%d", &num_length);

    char *str_input = new char[num_length];

    char *str_output = new char[num_length];

    printf("BCD2Bin press 1 and Bin2BCD press 2 and press Enter to confirm:");

    scanf("%d", &mode);

    if (mode == 1) {

        printf("Now Input the 8421 code you wish to convert to binary:");

        scanf("%s", str_input);

        printf("The result is:\n%s\n", _itoa(BCD2Dec(str_input), str_output, 2));

    }

    else if(mode == 2) {

        printf("Now Input the binary you wish to convert to 8421 code:");

        scanf("%s", str_input);

        printf("The result is:\n%s\n", _itoa(Bin2BCD(str_input), str_output, 2));

    }

    else {

        printf("Wrong Code!");

    }

 

    return 0;

}

 

int Bin2BCD(char *str)

{

    int num_return = 0;

    char *str_local = str;

    while (*str_local != '\0') {

        num_return = (num_return << 1) + (*str_local - 0x30);

        str_local++;

    }

    int num_return2 = 0;

    int index = 1;

    while (num_return != 0) {

        num_return2 += num_return % 10 * index;

        num_return /= 10;

        index *= 16;

    }

    return num_return2;

}

int BCD2Dec(char *str)

{

    char *str_local = str;

    char str_4[4] = { 0 };

    int num_return = 0;

    while (*str_local != '\0') {

        strncpy(str_4, str_local,4);

        num_return = 10 * num_return + 8 * (str_4[0] - 0x30) + 4 * (str_4[1] - 0x30) +

            2 * (str_4[2] - 0x30) + 1 * (str_4[3] - 0x30);

        str_local += 4 * sizeof(char);

    }

    return num_return;

}

结果图:



4.请利用宏来实现3题功能。

答:

程序源码:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

#define ToDec(x)    (x%10 + (x/10%10)*2 + x/100%10*4 + x/1000*8)

#define BCD2Bin(x)   _itoa(ToDec(x%10000) + ToDec(x/10000)*10, str_output, 2)

#define Bin2BCD(x)   _itoa((ToDec(x)>10)?ToDec(x)/10*16+ToDec(x)%10:ToDec(x),str_output,2)

int main(void)

{

    int mode = 0;

    int str_input;

    char str_output[8];

    int x = 10101;

    int y = ToDec(x);

    int z = ToDec(x % 1000) + ToDec(x / 1000) * 10;

    printf("BCD2Bin press 1 and Bin2BCD press 2 and press Enter to confirm:");

    scanf("%d", &mode);

    if (mode == 1) {

        printf("Now Input the 8421 number you wish to convert to binary:");

        scanf("%d", &str_input);

        printf("The result is:\n%04s\n", BCD2Bin(str_input));

    }

    else if(mode == 2) {

        printf("Now Input the binary you wish to convert to 8421 code:");

        scanf("%d", &str_input);

        printf("The result is:\n%08s\n", Bin2BCD(str_input));

    }

    else {

        printf("Wrong Code!");

    }

    return 0;

} 

效果截图:

5.请利用来实现一个抽象数据类型的链表,他可以存放任意自定义数据类型,至少实现指定位置插入和制定位置得到数据,以及制定位置删除。

#include <stdio.h>

struct Node

{

    struct Node* next;

    void* data;

};

 

typedef struct bearList

{

    bearList() {

        head = new Node;

        head->data = 0;

        head->next = NULL;

    }

    ~bearList() { delete head; }

    void createbearList(int n);

    void InsertNode(int position, int d);

    void *GetNode(int position);

    int GetLength();

    void DeleteNode(int position);

    void DeleteList();

    struct Node *head;

} bear;

 

void bearList::createbearList(int n)

{

    if (n < 0) {

        printf("Input Wrong!");

    }

    else

    {

        Node *pnew, *ptemp;

        ptemp = head;

        int i = n;

        while (n-- > 0) {

            pnew = new Node;

            printf("Input No.%d data for the list.", i - n);

            scanf("%d", (int*)&((pnew->data)));

            pnew->next = NULL;

            ptemp->next = pnew;

            ptemp = pnew;

        }

    }

}

 

void bearList::InsertNode(int position, int d)

{

    if (position < 0 || position > GetLength() + 1) {

        printf("Wrong Position!");

    }

    else {

        Node *pnew, *ptemp;

        ptemp = head;

        pnew = new Node;

        (pnew->data) = (void*)d;

        pnew->next = NULL;

        while (position-- > 1) {

            ptemp = ptemp->next;

        }

        pnew->next = ptemp->next;

        ptemp->next = pnew;

    }

}

 

void * bearList::GetNode(int position)

{

    Node *ptemp;

    ptemp = head->next;

    int i = 1;

    while (position - i++) {

        ptemp = ptemp->next;

    }

    return ptemp->data;

}

 

int bearList::GetLength()

{

    Node *p = head->next;

    int n = 0;

    while (p != NULL) {

        n++;

        p = p->next;

    }

    return n;

}

 

void bearList::DeleteNode(int position)

{

    if (position < 0 || position > GetLength()) {

        printf("Wrong Position!");

    }

    else {

        Node *ptemp = head, *pdelete;

        int i = position;

        while (i-- > 1) {

            ptemp = ptemp->next;

        }

        pdelete = ptemp->next;

        ptemp->next = pdelete->next;

        delete pdelete;

        pdelete = NULL;    

    }

}

 

void bearList::DeleteList()

{

    int i = GetLength();

    while (i-- > 0) {

        DeleteNode(i);

    }

}

 

int main(void)

{

    bearList bear;

    bear.createbearList(3);

    bear.InsertNode(2,5);

    int i = (int)bear.GetNode(2);

    bear.DeleteNode(3);

    bear.DeleteList();

    return 0;

}

结果:

1.     初始化链表

2.     在位置2处插入数据5

3.  查询位置2处的数据

4.     删除位置3的数据

    

5. 清空空链表



共8条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]