C语言易错点整理

Chapter4 - Swicth

if else语句可能出现少打花括号的问题
逻辑运算 &&、||短路特性
(i != 0)&&(j / i >0)保证了除0运算不会发生

switch

若所有的case常量都不能与表达式的值相匹配,那么就执行default后面的语句。通常用于处理不在合理区间内的非法数据。

程序执行到switch语句时,先计算表达式的值,然后自上而下寻找与该值匹配的case常量,找到后则按顺序执行此case后的所有语句,而不再进行判断,直到遇break语句或右花括号}为止。

case后面的表达式不能是含有变量的关系表达式和逻辑表达式,它不能像if语句那样起到判断表达式真假的作用。

数值溢出、截断

如 int转short

SoftwareTesting、浮点数技巧

Ex:判断三角形的类型
10,10,14.14(10 2 \sqrt{2} 2 的近似值),不能直接用“==”判断,
CSAPP中也讲过,浮点数比较相等通常使用作差法(fabs<设定的 误差值)

Ex:浮点数判0

#include  <math.h>
#define   EPS 1e-6
if (fabs(disc) <= EPS)    /* 判别式等于0时,... */

一旦遇到除法,无论整形还是浮点,必须除数判零

Chapter6-7 ModuleDesign & Function

v尾递归:不返回,一般是加参数

Chapter9 Pointer——Intro

1.指针变量未初始化就直接使用

(定义指针变量可以不初始化,但使用时一定要)

void swap(int *x,int *x)
{
    int *pTemp;
    *pTemp = *x;
    *x = *y;
    *y = *pTemp;
}
//本函数不可以实现交换

2.即使是数组元素,swap也必须是传地址
3.函数指针

float integral(float(*f)(float),float a,float b)
{
    float sum = 0;
    for(int i = 0;i < N;i ++)
    {
        sum += f(i/N)*(b-a)/N;
    }
    return sum;
}

2.空指针和void指针

int *p = NULL;//代表“无效指针”(指向为空)
void *q;//代表“可指向任意类型”的指针

关于void指针,典型的例子就是
void *malloc(size_t size)

Chapter10 String

1.字符指针的两种写法-指向常量存储区和指向runtime数组

    #case 1 字符串存储常量存储区,用指针指向
    char *s = {"abcd"};
    char *a=s;//将保存在常量存储区的“Hello”首地址赋值给a(a指向该静态存储区)
    *a = 'c';//错误语句!!不能对常量修改,指针仅仅是指向

    #case 2 字符串存储在数组中
    char s[] = {"abcd"};
    char *a=s;//a指向该数组,等价于 a = &s[0]
    *a = 'c';//完全正确

2.(字符)指针做参数,按传值

1.在c语言中实参和形参之间的数据传输是单向的“值传递”方式,也就是实参可以影响形参,而形参不能影响实参。
指针变量作为参数也不例外,但是可以改变实参指针变量所指向的变量的值。

#include <stdio.h>

unsigned MyStrlen(char *p)//char *p做参数,指针的值做参数,和swap(a,b)原理相同
{
    unsigned int len;
    len = 0;
    for(;*p != '\0';p++)//p作为参数,却执行了自增操作
    {
        len ++;
    }
    return len;
}
int main()
{
    char *p = "abcd";
    char *ptr = p;
    printf("%d",MyStrlen(p));
    //也可写成如下
    char p[] = "abcd";
    printf("%d",MyStrlen(p));
}

上例中的ptr指针,指针可以自增,但

2.再看一个例子

void inversePointer(char *str)//字符指针做参数
{
    char s[N];
    char *t = s;
    strcpy(t,str);
    while(*(t+1) != '\0')//points at the last char
    {
        t ++;
    }
    while(*(str+1) != '\0')
    {
        *str = *t;
        str ++;
        t --;
    }
}

本例中,str做参数,在函数内部运算地花里胡哨,但出了函数,还是恢复指向字符串的首地址,因为在函数中只是临时拷贝的地址用于做参数并运算

3.在看一个例子(忽略了数组名做参数的本质 (传地址),冗余定义变量)

void MyStrcat(char dst[],char src[])
{
    char *p = dst,*q = src;//实际上可以直接用dst、src进行运算,这是参数的本质
    while(*p != '\0')
    {
        p++;
    }
    while(*q != '\0')
    {
        *p = *q;
        p++;
        q++;
    }
    *p = '\0';
}

修改如下

void MyStrcat(char dst[],char src[])
{
    //MyStrcat的形参是数组,但实际上传的实际参数是一个地址,他是按指针来处理的,所以可以这么用(自增),
    while(*dst != '\0')
    {
        dst++;
    }
    while(*src != '\0')
    {
        *dst = *src;
        dst++;
        src++;
    }
    *dst = '\0';
}

3.字符串数组与指针数组的对比

用数组名法定义字符串,不可以用数组名进行“自增”等运算

    //正确形式
    char *p = "abcd";
    p ++;
    //错误形式
    char p[] = "abcd";
    p ++;//直接报错,因为p这里是数组名

    //错误形式更正如下
    char str[] = "abcd";
    char *p = str;
    *(p+2) = 'a';//可对字符串进行修改

4.字符串target造副本t

(实现删除某字符、插入空格 10.5、10.6)

char str[N];
char *t=str;
strcpy(t,target);

5.字符串一定要’\0’结尾!

特别是在Mystrcat、Mystrcpy等自定义函数中

Chapter11 Pointer——Advanced

note:指针的解引用就是*这个符号,从该地址取出值

1.二维数组和指针数组

二维数组 int (*a)[N]

int (*a)[10] 指向一个整型的一维数组,这个一维数组的长度是10,也可以说是a的步长。也就是说执行a+1时,a要跨过10个整型数据的长度;
int a[10] 这个是最基本的数组,不多说了

5单选(1)
char (*p)[10];该语句定义了一个

A.指向长度为10的字符串的指针变量p    
B.10个元素的指针数组p,每个元素存放一个字符串
C.10个元素的指针数组p,每个元素可以指向一个字符串
D.指向含有10个元素的一维字符型数组的指针变量p
指针数组 int *a[N]

指针数组怎么做参数?

指针数组做参数

以string为例:

#include <stdio.h>
void  Print(char *arr[], int len);  
int main()
{
    char *pArray[] = {"How","are","you"};
    int   num = sizeof(pArray) / sizeof(char);//此处应÷sizeof(char *),因为指针数组里面不是存的字符串,是固定长度的地址(本机为64位)
    printf("Total string numbers = %d\n", num); 
    Print(pArray, num);
    return 0;
}

void  Print(char *arr[], int len)
{
    int  i;    
    for (i=0; i<len; i++)
    {
        printf("%s ", arr[i]);
    }
    printf("\n");
}

指针数组做比如“自增运算”?

int *p[10];
        p ++;

2.二维数组列指针

int a[3][3] = { 1,2,3,4,5,6,7,8,9 } ;

int s = 0 ;
for( int *p = a[0] ; p < a[0] + 9 ; p++ )
{
    s += *p  ;
}

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

3.二维数组传参数

注意 函数定义时参数写法、调用时参数传法

方法一:二维数组直接做参数
必须指定列数
方法二:二维数组行指针做参数 int (*p)[N]

本质上,这是二维到二维的拷贝传递

参数拷贝传递方法:

int (*p)[N];
int a[N][N] = {...};
p = a;
printf("%d",p[0][1]);//完全正确!!
行指针二维数组--打印一个元素的值:(标准答案)
printf("%d ",*(*(a+i)+j));//*(a+i)+j即是元素的地址,swap就用它
而不是*(a+i)[j]//没有这种用法,目前是没用碰到,直接使用的话,部分是可以正常打印的,但会出现越界(不打印数组元素而是打印地址),就很奇怪,暂且只记住标准用法
03-16 修改为(*(a+i))[j]就对了(改成a[i][j]同样正确!!)

方法三: *a是a[0][0]的地址

本质上,这是二维到一维的拷贝传递

重要等式
int *s = a[0];
则有:
a[0] == &a[0][0] == *a

1、二维数组直接做参数
void Transpose(int a[][N],int n)//必须指定列数
Tansepose(a,n);//调用

2、二维数组行指针做参数
void Transpose(int (*a)[N],int n)//必须指定列数,其实就和第一种写法完全一样
{
    int i,j;
    for..
        for..
            swap(*(a+i)+j,*(a+j)+i);
        
}
Tansepose(a,n);//调用

3、二维数组的“列指针”做实参
void Transpose(int *a,int n);
调用方法如下
int a[3][3] = { 1,2,3,4,5,6,7,8,9 } ;
int *s = a[0];
//等价于int *s = *a;
//等价于int *s = &a[0][0];
Transpose(s,n);//实际上就是把地址&a[0][0]传进去

做函数参数时一定要指定列大小
例题1:
单选(1分)
设有以下定义:
int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int (*ptr)[3] = a;
int *p = a[0];
则以下能够正确表示数组元素a[1][2]的表达式是

A.((ptr + 1) + 2)
B.(ptr + 1) + 2
C.
(
(ptr + 1) + 2)
D.
(*(p + 5))

//我的分析:D选项多了一个*,因为p指向a[0],而a[0]&a[0][0]*a

例题2:
有以下程序段,则*(p[0]+1)所代表的数组元素是:
#include <stdio.h>
int main()
{
int a[3][2]={1,2,3,4,5,6,},*p[3];
p[0]=a[1];

return 0;
}

A.a[1][2]
B.a[1][1]
C.a[1][0]
D.a[0][1]

例题3:花式指二维数组
12单选(1分)
在以下程序段中的空白处填写适当的表达式或语句,使程序能正确引用c数组元素。

#include <stdio.h>
int main()
{
int c[4][5],(*p)[5],i,j,d=0;
for(i=0;i<4;i++)
{
for(j=0;j<5;j++)
{
c[i][j]=d;
d++;
printf("%4d",c[i][j]);
}
printf("\n");
}
p=c;
printf("%d,%d\n",____________);
return 0;
}

A.p+1,c[0][1]
B.(p[0]+2),c[0][2]
C.
(p+1)+3,c[1][3]
D.*(p+3),c[0][3]

例题4单选(1分)
若二维数组a有m行n列,则下面能够正确引用元素a[i][j]的为

得分/总分

A.((a+i)+j)
B.(a+jn+i)
C.(a+in+j)
D.*(*a+i)+j

正确答案:A你错选为C



### 数组寻址,一定注意行列长度是开始就给定的
```c
void  Total(int *score, int sum[], float aver[], int m, int n)
{
        int  i, j;
        for (i=0; i<m; i++)
        {
            sum[i] = 0;
            for (j=0; j<n; j++)
            {
                sum[i] = sum[i] + *(score + i * COURSE + j);//这里的列长度应该用数组初始化时的,而不是用户输入的
            }
            aver[i] = (float) sum[i] / n;
        }
}

Chapter12 Struct

1.struct定义

1.必须加分号
2.背诵定义的格式:

例题:
2单选(1)
以下选项中不能正确把cl定义成结构体变量的是

得分/总分

A.
typedef struct
{ 
    int red;
    int green;
    int blue;
} COLOR;
COLOR cl;


B.
struct
{ 
    int red;
    int green;
    int blue;
} cl;

0.00/1.00

C.
struct color cl
{ 
    int red;
    int green;
    int blue;
}


D.
struct color 
{ 
    int red;
    int green;
    int blue;
} cl;

正确答案:C你错选为B

2.结构体传参

1.按值传递

void print(Darray aPtr)

2.按引用传

void grow(Darray *aPtr,int len)

动态内存分配

动态数组基本操作

typedef struct 
{
    int *array;//动态数组的地址
    int length;//数组长度
}Darray;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJtsICSk-1616679333969)(/assets/Darray_iy69r6s3k.png)]

Darray create(int len)
{
    Darray a;
    a.array = (int *)malloc(sizeof(int)*n);
    ...
    ...
}

void grow(Darray *aPtr,int len)//增长到len长度
{
    //思路就是开一个新数组
    int *p = (int *)malloc(sizeof(int)*n);
    ...
    //一定要free
    free(aPtr->array);
}

动态数组经典错误

1. 未初始化直接使用
如果使用malloc,最好用memset()进行清零操作,calloc()能自动将分配的内存初始化为0
2. 操作越界
使用strcpy()、gets()、memcpy()
3. free§

free§之后,若要再使用p,需要先p=NULL,否则是野指针[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LXqZ1Gh0-1616679333971)(/assets/多指针指向同一块内存_cr0vmck62.png)]
解决对策:
1尽量把free集中在函数出口
2
若不能,free后立即置NULL

4. 从函数中返回局部变量的地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aa7Lafug-1616679333974)(/assets/函数中返回地址_d0yah412b.png)]
5. 使用pointer不初始化或分配内存

错误示范://把注释部分去除即可

#include <stdio.h>

void GETINPUT(char *s)
{
    scanf("%s",s);
    puts(s);
}
int main()
{
    char *p = NULL,
    //char str[80];
    //p = str;
    GETINPUT(p);
    puts(p);
}

正确示例1(在函数中分配内存)


#include <stdio.h> 

void GETINPUT(char *s)
{
    s = (char *)malloc(80);
    scanf("%s",s);
    puts(s);
}
int main()
{
    char *p = NULL;
    GETINPUT(p);
    puts(p);
}

正确示例2

#include <stdio.h>
char* GETINPUT()
{
    char *s;
    s = (char *)malloc(80);
    scanf("%s",s);
    puts(s);
    return s;
}
int main()
{
    char *p = NULL;
    p = GETINPUT();
    puts(p);
}

未回收内存
  1. 忘记free
    必须free!
  2. 乱指
char *p,*q;
p = malloc..
q = malloc..
q = p;//结果q原来指向的内存块变成了“孤魂野鬼”
  1. 程序结束后,内存都被回收了吗?
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值