C语言经典100示例——学习记录(中)

兮城

关注

阅读 113

2022-03-30

目录

41,学习static定义静态变量的用法。

42,学习使用auto定义变量的用法。 (一般不用)

 44,学习使用external的用法

45,学习使用register定义变量的方法。(一般不用)

46,宏#define命令练习。

【 #define与typedef区别】

    【宏定义的好处】

【有参宏与函数区别】

49,#if #ifdef和#ifndef的综合应用。

50,#include 的应用练习。 

      include后面用<>和""是有一定的区别的

51~53按位与、或、异或

        位运算符有:&(按位与)、|(按位或)、^(按位异或)、~ (按位取反)。

1、按位与 

2、按位或运算符(|)

3、异或运算符(^) 

4、取反运算符(~)

5、左移运算符(<<) 

6. 右移运算符(>>)

7. 复合赋值运算符


41,学习static定义静态变量的用法。

1.【静态局部变量】:静态局部变量的效果跟全局变量有一拼,但是它位于函数体内部,极有利于程序的模块化。

#include <stdio.h>

void fn(void)
{
    int n = 10;

    printf("n=%d\n", n);
    n++;
    printf("n++=%d\n", n);
}

void fn_static(void)
{
    static int n = 10;

    printf("static n=%d\n", n);
    n++;
    printf("n++=%d\n", n);
}

int main(void)
{
    fn();
    printf("--------------------\n");
    fn_static();
    printf("--------------------\n");
    fn();
    printf("--------------------\n");
    fn_static();

    return 0;
}

   运行结果如下:

n=10
n++=11
--------------------
static n=10
n++=11
--------------------
n=10
n++=11
--------------------
static n=11
n++=12

--------------------------------


2.【静态全局变量】:静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响
        能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用
3.【静态函数】:静态函数只能在声明它的文件中可见,其他文件不能引用该函数;不同的文件可以使用相同名字的静态函数,互不影响

42,学习使用auto定义变量的用法。 (一般不用)

       C语言中提供了存储说明符   auto,register,extern,static   说明的四种存储类别。四种存储类别说明符有两种存储期:自动存储期  和  静态存储期

       其中auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块是被建立,它在该程序块活动时存在,退出该程序块时撤销。

      关键字auto,它可用于定义局部变量。但自从所有的非全局变量的缺省值假定为auto以来,auto就几乎很少使用了,

       有些时候并不能很确定一个变量应该具备的数据类型,此时auto关键字可以派上用场。auto存储类型说明符声明了一个自动变量,auto对象和变量被存储在栈中,它的生命周期仅存在于它的声明所在的块(block)中,即一个只在块运行时有效的变量。auto变量的声明能包含初始化。因为有auto存储类型的变量并不自动的初始化,应该在声明时显式的初始化它们,或在同一个块内赋给它们初始值。未初始化的auto变量的值是未定义的。

 44,学习使用external的用法

      在源文件A里定义的函数,在其它源文件里是看不见的(即不能访问)。为了在源文件B里能调用这个函数,应该在B的头部加上一个外部声明:

  这样,在源文件B里也可以调用那个函数了。   
  【注意区别】:在A里是定义,在B里是声明。

      一个函数只能(也必须)在一个源文件里被定义,但是可以在其它多个源文件里被声明定义引起存储分配,是真正产生那个实体。而声明并不引起存储分配。打一个粗俗的比方:在源文件B里声明后,好比在B里开了一扇窗,让它可以看到A里的那个函数。

     与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。

45,学习使用register定义变量的方法。(一般不用)

     使用register  暗示编译程序相应的变量将将被频繁使用,因此尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率,加快存取速度。【尽可能----但不是绝对!】

      使用register修饰符有几点限制:

1、register变量必须是能被CPU寄存器所接受的类型,这通常意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。但是,有些机器的寄存器也能存放浮点数。

2、因为register变量可能不存放在内存中,所以不能用取址符运算符“ & ”来获取地址

46,宏#define命令练习。

        宏不是语句,结尾不用加“;”,否则会被替换进进程中

        #表示这是一条预处理指令

        【#运算符】:#的作用就是将#后边的宏参数进行字符串的操作,也就是将#后边的参数两边加上一对双引号使其成为字符串。例如a是一个宏的形参,则替换文本中的#a被系统转化为"a",这个转换过程即为字符串化。

       【##运算符】: ##运算符也可以用在替换文本中,它的作用起到粘合的作用,即将两个宏参数连接为一个数

#define TEST(param1,param2) (param1##param2)

int num =TEST(12,34);
printf("num=%d\n",num);

输出结果为:num=1234

举例1:(带参数的宏定义)

#include<stdio.h>
#define SQ(x) (x)*(x)    //这是带参数的宏定义
int main()
{
	printf("%d",9/SQ(3)) ;
}

 输出的结果为

     使用#define定义的代码,在编译前是直接替换程序中的代码, 所以实质上计算的是

【 #define与typedef区别】


 

举例2:

#define SQUARE(x) x*x
int main()
{
	int a = 5;
	printf("SQUARE(a): %d\n",SQUARE(a));	//这个值为25
	printf("SQUARE(a+1): %d\n", SQUARE(a + 1));	//第一反应是打印36,其实打印的是11
}

            这样输出的结果是

        【分析】:编译器在处理SQUARE(a+1)时,原本运算应为:(a+1)*(a+1),实际所做运算为 a+1*a+1 = 2*a+1 ——因为没有加括号,运算符的优先级发生变化,导致运算的顺序出错。

       要想能够真正使用好宏定义,那么在读别人的程序时,一定要记住先将程序中对宏的使用全部替换成它所代表的字符串不要自作主张地添加任何其他符号,完全展开后再进行相应的计算,就不会写错运行结果。如果是自己编程使用宏替换,则在使用简单宏定义时,记得加上括号表示优先级

举例3:——将变量宏定义为一种字符

#define LAG >
#define SMA <
#define EQ ==
#include <stdio.h>
int main()
{
    int i,j;
    printf("请输入两个数字:\n");
    scanf("%d %d",&i,&j);
    if(i LAG j)
        printf("%d 大于 %d \n",i,j);
    else if(i EQ j)
        printf("%d 等于 %d \n",i,j);
    else if(i SMA j)
        printf("%d 小于 %d \n",i,j);
    else
        printf("没有值。\n");
    return 0;
}

    【宏定义的好处】

1、方便程序的修改

        如:π的值根据精度要求,可以变化

2、完成简单的函数调用功能,提高程序运行效率

        在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,而宏定义是在预处理阶段即进行了 宏展开,在执行时不需要转换,效率较高。

【有参宏与函数区别】

  • 在宏定义#define COUNT(M) M*M中的形参不分配内存单元,不做类型定义,只是简单的文本替换,而函数int count(int x)中形参x是局部变量,会在栈区分配内存单元,所以要做类型定义,而且实参与形参之间是值传递。而宏只是符号代换,不存在值传递。

        宏定义也可以定义表达式或多个语句

#define A(a,b,c) ({a=1;b+=1;c=3;a+b+c;})
#include <stdio.h>
int main()
{
	int a;
	int b=1;
	int c;
	int d;
	d=A(a,b,c);
	printf("%d,%d,%d,%d\n" ,a,b,c,d);
	return 0;
}

输出为   1,2,3,6

这篇文章关于宏定义的解释很详细——

宏定义#define详解 - long_ago - 博客园

49,#if #ifdef和#ifndef的综合应用。

#define:定义一个预处理宏
#undef :取消宏的定义

#if :编译预处理中的条件命令,相当于C语法中的if语句
#ifdef :判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef: 与#ifdef相反,判断某个宏是否未被定义
#elif :若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else :与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif: 条件命令的结束标志.(条件命令有——#if, #ifdef, #ifndef)
defined :与#if, #elif配合使用,判断某个宏是否被定义

#include <stdio.h>


#define MAXIMUM(x,y)(x>y)?x:y
#define MINIMUM(x,y)(x>y)?y:x
int main(){

int a=10;
int b=20;

#define MAX //定义一个预处理宏MAX

//如果这个宏定义被定义,则执行随后语句
#ifdef MAX
    printf("1The lager one is %d\n", MAXIMUM(a,b));
#else
    printf("2The lower one is %d\n", MINIMUM(a,b));
#endif

#ifdef MIN
    printf("3The lower one is %d\n", MINIMUM(a,b));
#else
    printf("4The lager one is %d\n", MAXIMUM(a,b));
#endif

#undef MAX //取消宏定义MAX
    
#ifdef MAX
    printf("5The lager one is %d\n", MAXIMUM(a,b));
#else
    printf("6The lower one is %d\n", MINIMUM(a,b));
#endif

#define MIN//定义一个预处理宏MIN
#ifdef MIN
    printf("7The lower one is %d\n", MINIMUM(a,b));
#else
    printf("8The lager one is %d\n", MAXIMUM(a,b));
#endif

}

输出的结果为:——【即输出了第 1 4 6 7条语句】

50,#include 的应用练习。 

        扩展名为.h的文件,在C语言中被称为header file, 也就是头文件。

 引用其它头文件,格式为    #include <xxx.h>    或    #include "xxx.h"

      include后面用<>和""是有一定的区别的

        在编译器查找头文件的时候,会在两个区域分别查找。一个是 系统头文件区域 ,即类似于stdio.h一类的C库函数头文件区。另一个是自定义头文件区,比如当前目录,以及其它自定义的目录。用<>时,编译器会先在系统区域查找,然后再查找自定义区域。而用" "时则相反。

51~53按位与、或、异或

        位运算符有:&(按位与)、|(按位或)、^(按位异或)、~ (按位取反)。

        优先级从高到低,依次为~、&、^、|

        【有个口诀】:单 算 移 关 与,异 或 逻 条 赋

        具体含义:单目运算符——算术运算符(+-*/)——移位——关系运算符(大小比较)——按位与——按位异或——按位或——逻辑运算符——条件运算符——赋值运算符

1、按位与 

        按位与操作 0&0=0; 0&1=0; 1&0=0; 1&1=1;(负数按补码形式参加按位与)

                ——注意,负数得到的结果仍是补码,需要转回原码得到十进制数值。

  • 例子:10&9: 0000 1010 & 0000 1001 = 0000 1000 = 8
  • 与运算”的特殊用途:
    (1)清零。如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
    (2)取一个数中指定位
    方法:找一个数,对应X要取的位,该数的对应位为1,其余位为零,此数与X进行“与运算”可以得到X中的指定位。

2、按位或运算符(|)

  • 参加运算的两个对象,按二进制位进行“或”运算。

  • 运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;

  • 例如:3|5 即 0000 0011 | 0000 0101 = 0000 0111 因此,3|5的值得7。

  • 负数按补码形式参加按位或运算,得到的结果仍是补码,需要转回原码得到十进制数值。

  • 或运算”特殊作用:常用来对一个数的某个位置,令其为1——对应X要取的位,该数的对应位为1,其余位为零

3、异或运算符(^) 

  • 参加运算的两个数据,按二进制位进行“异或”运算。

  • 运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;——即,相同为0,相异为1

  • 【负数 举例】例如:10 ^ -9 即 0000 1010 ^ 1111 0111 = 1111 1101(补码) 原码即为1000 0011 即10^-9 = -3

  • “异或运算”的特殊作用:

    (1)使特定位翻转 找一个数,对应X要翻转的各位,该数的对应位为1,其余位为零,此数与X对应位异或即可。

        (2)与0相异或,保留原值 ,X ^ 0000 0000 = 1010 1110。

        (3)交换a和b【两种巧妙的方法】

   通过  异或 来交换:                 
                    a = a ^ b;

                    b = a ^ b;

                    a = a ^ b;


    通过   加减法  来交换
                    a = a - b;
                    b = a + b;
                    a = b - a;

 【异或---分析】:首先用a保存了a^b的 值,再用b = a ^ b = (a^b)^b = a^b^b = a^(b^b)=a^0=a,这样就成功的实现了b = a;接着又用a = a^b = (a^b)^b(第一个b还是原来的b,而第二个b已经是a的值,因为前面已经实现了交换)= (a^b)^a = a^a^b = 0^b = b,这样就实现了a = b;于是,就成功的实现了a,b两个值的交换。

【加减法---分析】:首先用a保存a-b的值,再用b=a+b=(a-b)+b=a;实现了b=a;然后用a=b-a (此时,b=a,而a=a-b)=a-(a-b)=b;实现了a=b。于是,就成功的实现了a,b两个值的交换。

4、取反运算符(~)

        取反,即0变1,1变0;

  • 使一个数的最低位为零,可以表示为:a&~1。

  • ~1的值为1111111111111110,再按“与”运算,最低位一定为0。因为“~”运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高

5、左移运算符(<<) 

        将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

        若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

6. 右移运算符(>>)

        将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

        无符号右移运算符(>>>):右移后左边空出的位用零来填充。移出右边的位被丢弃。

7. 复合赋值运算符

 

不同长度的数据进行位运算

        如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算

精彩评论(0)

0 0 举报