c语言易错点与细节总结

CLASS1 基本数据类型:

1.数据类型:固定内存大小的别名。是创建变量的模子。用于声明变量(4个字节大小的数据类型为int )

Char:一个字节

Short:两个字节

Int:四个字节

2.变量:一段实际连续存储空间的名字。通过变量来申请并且命名存储空间。变量的内存取决于其所属的数据类型。(3000-3004的存储空间的名字为变量i)知道存储空间名字之后,便可方便使用指定的内存空间。

3.指针:也是变量,存储的是另一个变量的地址

4.变量与类型的区别:

Sizeof(i/int):得到变量/数据类型所占的内存大小

例1:

打印出来:4,4

例2:typedef定义数据类型的关键字:

#include

typedef int INT32;//INT32是新的数据类型,由int定义,即可代替int

typedef unsigned char BYTE;//同理BYTE也是新的数据类型,由unsigned char定义

typedef struct _tag_ts//_tag_ts是啥不了解,等复习了结构体再更改。

{

    BYTE b1;

    INT32 i;

    short s;

}TS;//TS代表上述结构体

 

int main()

{

    INT32 i32;

    BYTE b32;

    TS ts;//声明ts变量为结构体

    printf("%d,%d\n",sizeof(INT32),sizeof(i32));

    printf("%d,%d\n",sizeof(BYTE),sizeof(b32));

    printf("%d,%d\n",sizeof(TS),sizeof(ts));

    getchar();

    return ;

}

 

CLASS2有符号和无符号

符号位:数据类型的最高位,用于标识数据的符号。

最高位为1,数为负数

最高位为0,数为正数

练习:

#include

int main()

{

    int sign=0;

    char i=-5;//char一个字节的内存,8位,数字二进制存储,-5记为11111001,规则看下一知识点

    short j=5;

    int k=-1;

    sign=(i&0x80);//0x为十六进制,转化为二进制10000000&按位与,即i为负数,则结果不为零,i为正数,结果为0

    printf("%d",sign);

    getchar();

    return 0; 

}

结果: sign非零

 

有符号数在计算机内部用补码表示:正数的补码是本身,负数补码是负数的各位取反后加1。

例2,表示7或-7:

7->111->00000111

-7->11111000+1->11111001

无符号数在计算机内部用原码表示,因为默认为正数,没有符号位。

无符号数最小为0。最大值受他所占的位数决定。

所以MAX_VALUE+1=MIN_VALUE或MIN=MAX-1

 

C语言中,变量默认为有符号数,unsigned声明变量为无符号数,signed表示有符号数。

只有整数类型(int,short,long)能够声明unsigned变量,浮点数不行。

#include

int main()

{

    int i;//默认为带符号整形

    unsigned int j;//声明变量为无符号整形

    signed int k;//声明变量为有符号整形

    return 0;

}

例:求结果

#include

int main()

{

    unsigned int i=5;

    int j=-10;

    if((i+j)>0)

    {

       printf("i+j>0");

    }

    else

    {

       printf("i+j>0");

    }

    getchar();

    return 0;

}

结果:!!!!!

无符号数和有符号数进行运算,有符号数自动转化为无符号数进行运算,结果为无符号数,一定大于零(-10为int(4字节,32位),转为32位2进制,同时负数首位为1,数字愈发大,为111111111。。。。。。。)

 

例:

#include

int main()

{

    unsigned int i=0;

    for(i=9;i>=0;i--)

    {

       printf("%u\n",i);

    }

}

结果:无限个该些值

c语言易错点与细节总结_第1张图片

原因:无限:for的判断条件i>=0,无符号数最小值为0,无限满足条件。

为4294-----等数字,无符号数变为0后-1就变为最大值(最大值为4个字节(32位)的全1值)因为判断条件的设定,导致错误的使用了unsigned。因此需要慎重考虑有符号和无符号数。

 

CLASS3浮点数

浮点数在内存中的存储方式:符号位,指数,尾数,与int(整数)内存表示不同

 

符号位

指数

尾数

Float(4字节)

1位

8位

23位

Double(8字节)

1位

11位

52位(0-51)

Float和double,分别表示不同的数值范围和精度

浮点数的转换:

  1. 浮点数转换为二进制
  2. 科学记数法表示二进制浮点数
  3. 计算指数偏移后的值

注意:计算指数时需要加上偏移量,

例:对于指数6,偏移后的值:

Float :127(固定值)+6——>133

Double:1023+6——1029

例:实数8.25在内存中的float表示:

8.25二进制表示:1000.01(0.25——4的-1次)科学记数法表示——>1.00001*(2^3),二进制,后退几个小数点,2的几次。得到指数3.

符号位:0(为正数)

指数:127+3——>130——>10000010(转成8位2进制)

小数00001

所以在内存中的存储为;

  1. 10000010  00001(全0补齐位数)

例:编程实验:10进制浮点数的内存表示

 

#include

int main()

{

    float f=8.25;

    unsigned int *p=(unsigned int *)&f;//定义指针p,指向f的首地址

    printf("0x%08X\n",*p);//16进制的8位,p指针指向的

    getchar();

    return 0;

}

 

注意:int类型范围[-2^31,2^31-1]

Float类型范围[-3.4*10^38,3.4*10^38]

Int和float都占4个字节内存,但是float比int范围大(浮点数表示范围更大):

原因:

  1. float能表示的数字个数与int相同(因为都为4字节)
  2. 但是float表示的数字不是连续的,存在间隙!
  3. float只是近似的表示法,不能做精确值。
  4. float内存表示法相对复杂,运算速度比int慢。
  5. double和float内存存储方式相同,所以double也不精确。但是double占用内存多,表示的精度更高。

验证float的不精确性

#include

int main()

{

    float f=3.1415f;

    float f1=123456789;

    printf("%0.10f\n",f);//打印到小数点后10

    printf("%0.10f\n",f1);

    getchar();

    return 0;

}

差距很大!说明float只是近似表示。

 

总结:

  1. 浮点类型与整数类型的表示方法不同。
  2. 浮点类型的表示方法更复杂
  3. 浮点类型可表示的范围更大
  4. 浮点类型的保存是不精确的
  5. 浮点类型运算速度慢

 

CLASS4类型转换

  1. 强制类型转换

Long l=800;

Int i=(int) l;

(1)语法:(Type)name

(2)转化的结果:目标类型能够容纳目标值:结果不变(short转化为int,

两个字节转为四个字节)

目标类型不能容纳目标值:结果产生截断(int转为char,四个字节转一个字节,把int的高三个字节扔掉了,只剩最低一字节)

(3)实例分析:

#include

struct TS

{

    int i;

    int j;

};

 

struct TS ts;

 

int main()

{

    short s=0x1122;

    char c=(char)s;//s为二字节,char一字节,保留低位,16进制占40.5个字节,所以保留两位16进制,c的结果0x22

    int i=(int)s;//不会截断,0x1122

    int j=(int)3.1415;//float类型,转为int,直接为3

    unsigned int p=(unsigned int)&ts;//ts的地址转为无符号整数,64位电脑64位的地址,8位转为4位,产生截断

    long l=(long)ts;//编译错误,无法将结构体转为基本类型

    ts=(struct TS)l;//编译错误,无法将基本类型转化为结构体

    return 0;

}

 

  1. 隐式类型转换

Short s=800;

Int i=s;

  1. 编译器主动进行的类型转换
  2. 同强制类,低类型到高类型安全,不截断,高类型到低类型,会截断。
  3. 何时发生隐式类型转换:

(安全的转换方向char,short—int—unsigned int—long—unsigned long—float—double)牢记何种转换安全!!

算术运算中,低类型转为高类型(如char和short进行运算,先char转short)

赋值表达,表达式的值转为左边变量的类型

函数调用,实参转化为形参的类型

函数返回值,return表达式转化为返回值

  1. 实例

#include

 

int main()

{

    char c='a';

    int i=c;

    unsigned int j=0x11223344;

    short s=j;

    printf("c=%c\n",c);

    printf("i=%d\n",i);//charint,是安全的,值为a

    printf("j=%x\n",j);//该数为4个字节,转化为4个字节的无符号整数,不会产生截断

    printf("s=%x\n",s);//4字节到2字节,发生截断,3344

    getchar();

    return 0;

}

结果:

 

 

总结

  1. 强制类型转换由程序员进行完成

转换可能产生截断

转换不区分类型的高低,但是可能产生结果的错误

转换不成功,编译器给出错误信息

  1. 隐式类型转换由编译器完成

低类型转高类型安全,反之不安全

 

CLASS5变量属性

1.变量的属性:定义变量时,加上属性关键字

2.属性关键字指名变量特有意义

使用语法

#include

int main()

{

    auto char i;

    register int j;

    static long k;

    extern double m;

 

    return 0;

}

 

3.关键字的分类:

(1)Auto关键字:auto是c语言中局部变量的默认属性,存储在栈上。

示例:

#include

int main()

{

    int i;//局部变量默认属性为auto

    auto int j;//声明auto属性

    return 0;

}

 

(2)register关键字:将局部变量存储于寄存器中,即为寄存器变量。

1)为什么要用寄存器变量:寄存器比内存速度要快的多多多。

2)Register只是请求寄存器变量,但不一定请求成功(寄存器有限)。

3)不能用&云算法获取register变量的地址,&只能获得内存的地址,不能获得寄存器的地址

例:

#include

register int g_v;//错误,在intmain外,全局变量,register只可用于声明局部变量

int main()

{

    register char var;//可以成为寄存器变量,具体要看是否有寄存器

    print("0x%08X",&var);

    return 0;

}

 

(3)static关键字:指名变量的“静态”属性,将局部变量存储在程序静态区(从栈移到静态区,与全局变量的生命周期相同,但只是工作在局部)

补充:全局变量与静态变量:

#include

int g_v;//全局变量,(1)程序任意地方均可访问(2)存储在程序的静态区,生命周期为程序运行到结束

static int g_vs;//全局变量,设为静态,只有当前文件中可以访问

 

int main()

{

    int var;//局部变量,auto关键字,在栈上分配空间

    static int svar;//局部变量,从栈移到静态区,生命周期改变

    return 0;

}

 

1)static还具有“作用域限定符”的意义(static修饰的全局变量作用域只是在声明的文件中,修饰的函数只是在声明的文件中)

 

2)实例分析:

#include

int f1()

{

    int r=0;//rauto的局部变量,在栈上分配空间,调用返回之后,局部变量消失

    r++;

    return r;

}

int f2()

{

    static int r=0;//唯一不同,rstatic的静态局部变量,在静态区分配空间,调用返回之后,局部变量不消失

    r++;

    r++;

    return r;

}

 

int main()

{  

    auto int i=0;//声明iauto,i为栈变量

    static int k=0;//局部变量k的存储区在静态区,作用域在main

    register int j=0;//向编译器申请将j存储在寄存器中

   

    printf("%p,",&i);

    printf("%p,",&k);

    printf("%p,",&j);//error&只能返回内存的地址

   

    for(i=0;i<5;i++)//

    {

       printf("%d,",f1());

    }

    for(i=0;i<5;i++)

    {

       printf("%d,",f2());

    }

    getchar();

 

    return 0;

}

 

分析;(1)I和k相邻定义,但是地址相差远:一个存在栈上,一个存在静态区。若存在同一个位置,则存储在相邻位置 。

(2)输出的11111,12345差异: 第一个r是auto的局部变量,在栈上分配空间,调用返回之后,局部变量消失。第二个r是static的静态局部变量,在静态区分配空间,调用返回之后,局部变量不消失,生命周期是程序的运行周期。

 

(4)extern关键字

1)声明“外部”定义的变量和函数(在文件的其他地方分配空间,定义)

2)用于告诉编译器用c方式编译

extern "c"

{

    int f(int a,int b)

    {

       return a+b;

    }

}

3)编程实验:static和extern使用

#include

 

extern int g_i;//告诉程序g_i在其他位置

int main()

{

    printf("%d\n",g_i);

    return 0;

}

 

int g_i;//全局变量定义在main后,需要extern提前声明

 

分析:如果全局变量在main后定义,会报错,需要在main前声明为extern

总结:1.auto变量存储在程序的栈,为默认属性

2.static存在程序静态区,作用域限定符,作用域缩小在文件中

3.register存储在寄存器中

4.extern的变量在程序其他位置分配空间,可以指示c++程序中,以c方式编译程序。

 

CLASS6分支语句

1.if语句:

1)if语句可独自存在

2)else不能单独存在,且与其最近的if相匹配

3)else语句后可以连接其他if语句

4)if语句中与0点的注意点:bool类型的变量直接出现在条件中,不要进行比较(if(b));

变量和0比较,0出现在符号左边,if(0==b),防止写成0=b;float变量不能直接进行0值比较,需要定义精度,因为float浮点数不精确。#define e 0.00001,float f=0.0     ,   if((f>=-e)&&(f<=e))

      

2.switch:

       1)switch语句对应,单个条件,多个分支的情况。

       2)case语句分支下必须有break,否则会造成分支重叠

       3)最后必须加上default.

switch(expression)

    {

       case CONST_1:

           //code

           break;//不加break则将后续Case内容都输出

       case CONST_2:

           //code

           break;

       default:

           //code

    }

Case语句中的值只能是整形或字符型

Case语句的排列顺序:按数字或字母顺序排列,正常放前面,异常放后面。

 

实例:

#include

void f1(int i)

{

    if(i<6)

    {

       printf("failed\n");

    }

    else if((i>=6)&&(i<8))

    {

       printf("good\n");

    }

    else//与上一个if匹配,相邻最近

    {

       printf("error");

    }

}

 

void f2(char i)

{

    switch(i)

    {

       case'c'://只能为常量,不可为变量,字母用单引号

           printf("correct\n");

           break;

       case'e':

           printf("error\n");

           break;

       case'r':

           printf("run\n");

           break;

       default:

           printf("unknow");

           break;

    }

}

 

int main()

{

    f1(5);

    f1(7);

    f2('c');

    getchar();

    return 0;

}

结果:

 

编程:将上面的switch和if对调

If块修改为,有点绕

    switch(i<6)

    {

       case(1):

           printf("failed\n");

           break;

       default:

           switch((i>=6)&&(i<8))

           {

              case(1):

                  printf("good\n");

                  break;

              default:

                  printf("error");

                  break;

           }

    }

Switch块修改为

void f2(char i)

{

    if('c'==i)//注意写法,常量写在左边,bug的主要来源,反写可能误写为赋值语句

    {

       printf("correct\n");

    }

    else if('e'==i)

    {

       printf("error\n");

    }

    else

    {

       printf("unknow");

    }

}

说明if和switch可以互换

 

 

小结:

  1. if语句适用于复杂逻辑判断的情形
  2. switch适用于离散型进行判断
  3. if和switch在功能上可以相互替换
  4. if对于按片分支判断的情形更加简洁
  5. switch对于多分支判断的情形更加简洁

 

 

 

 

 

CLASS7循环语句

  1. 基本工作方式:通过条件表达式判定是否运行循环体。
  2. Do,while,for的区别:
  1. do先执行后判断,循环体至少执行一次;
  2. while先判断后执行,可能一次都不执行。
  3. for先判断后执行,比while更简洁
  1. 各种循环回顾:

(1)do——while:

格式:(先do在判断条件,至少使用一次)

do

{

    //loop

}

while(condition)

 

(2)while

while(condition)

{

    //loop

}

 

 

(3)for

for(i=0;condition;i++)

{

    //loop

}

4.分别用三个办法实现累加从0到n的功能

考虑问题的全面性!!!!!

考虑正负问题:正数正常操作,负数输出

(1)do-while

#include

int f1(int n)

{

    int ret=0;

    if(n>0)//n大于0才执行

    {

       do

       {

           ret=ret+n;

           n--;

       }

       while(n>=0);//注意为小括号,非大括号

    }

    return ret;

}

 

int main()

{

    printf("%d",f1(-2));

    getchar();

    return 0;

}

 

结果:-2输入, ,也正确

发现,在do外还需要添加if语句,较为复杂

(2)while

int f1(int n)

{

    int ret=0;

    while(n>0)

    {

       ret=ret+n;

       n--;

    }

    return ret;

}

无需插入if语句,while相对于do-while更合适

(3)for循环

int f1(int n)

{

    int ret,i;

    for(ret=0,i=0;i<=n;i++)

    {

       ret=ret+i;

    }

    return ret;

}

 

 

5.break和continue的区别

1)Break表示终止循环的执行

Continu表示终止本次循环,进入下一次循环

Switch能否用continue?:不可以!!只能用在循环语句中

2)实例分析:do与break的妙用,工程中重要作用

#include

#include//malloc,向系统申请分配指定size个字节的内存空间

 

int func(int n)

{

    int i=0;

    int ret=0;

    int *p=(int*)malloc(sizeof(int)*n);//指针p动态的指向一堆空间,堆空间大小为传进来的n

 

    do

    {

       if(NULL==p) break;

       if(n<5) break;

       if(n>100) break;

       for(i=0;i<n;i++)

       {

           p[i]=i;

           printf("%d\n",p[i]);

       }

       ret=1;

    }while(0);//循环条件为0,表示只执行一次循环体!do-while循环的作用:有些break之后,强制执行free,释放空间

 

    free(p);//break后进入,释放p指针

    return ret;

}

 

int main()

{

    if(func(10))

    {

       printf("ok");

    }

    else

    {

       printf("error");

    }

    getchar();

    return 0;

}

与下一代码进行比较!!

#include

#include//malloc,向系统申请分配指定size个字节的内存空间

 

int func(int n)

{

    int i=0;

    int ret=0;

    int *p=(int*)malloc(sizeof(int)*n);//指针p动态的指向一堆空间,堆空间大小为传进来的n

 

    //do

    //{

       if(NULL==p) ret;

       if(n<5) return ret;

       if(n>100) return ret;

       for(i=0;i<n;i++)

       {

           p[i]=i;

           printf("%d\n",p[i]);

       }

       ret=1;

    //}while(0);//循环条件为0,表示只执行一次循环体!do-while循环的作用:有些break之后,强制执行free,释放空间

    printf("free p");

    free(p);//break后进入,释放p指针

    return ret;

}

 

int main()

{

    if(func(1))

    {

       printf("ok");

    }

    else

    {

       printf("error");

    }

    getchar();

    return 0;

}

输入1,结果:

发现没有printf(“free p”),没有释放指针空间,造成内存的泄露!!!

所以说:do-while是很好的防止内存泄露的方法。

 

小结:

  1. for循环先进行判断,在进行循环
  2. for适用于循环次数固定的场景
  3. while先进行判断在进入循环体执行。
  4. while适用于循环次数不固定的场合
  5. do-while先执行后判断,至少执行一次
  6. do-while(0),break结构,运用于防止内存泄露!!!!!

你可能感兴趣的:(c语言)