C语言知识点总结2

继续我的C语言学习与总结(注意:我的编译环境是:Keil uVision4.72,使用的是STM32系列MCU),以下问题的探讨都是累积起来的,一是给自己的学习带来方便,二来是在写时也可以加深印象,有时我会把这些知识点反复看至到自己完全掌握。

1、关于无符号数据类型与有符号数据类型的加减和比较大小的问题

这个问题我也一直比较模糊,关于加减法我做了如下测试,代码如下:

signed int x=-50;
unsigned int y=-40;	
signed int z=x+y;

变量z的结果为0xFFFFFFA6(我使用的是STM32系列MCU,int数据类型是32位的),这是保存的补码,实际的值为-90。那么是怎样得到这个结果的呢?

首先变量x是有符号类型,保存-50的补码,结果为0xFFFFFFCE,变量y是无符号类型,当给它赋一个负值时,会转换为2的32次方,然后减去这个负数的绝对值,结果为0xFFFFFFD8,实际也就是-40的补码,只不过它的最高位不是符号位,而是整个无符号数的一部分,是要参与计算的。

变量z是有符号类型,当执行无符号和有符号类型的相加减时,默认都先转换为有符号类型,因此变量y被看作有符号类型,最高位是符号位了,而变量x保持不变,最后结果就是0xFFFFFFA6,实际值就是-90。

接着上面的变量再来看下如下代码:

if (z > 0)
{
	printf("first branch!");
}
else
{
	printf("second branch!");//实际执行的语句
}

经调试发现,由于z是有符号类型,0这里默认会看作有符号类型,两个有符号类型作比较就不用转换,很明显-90小于0,所以会执行else后面的语句。

把上面的代码稍改一下,则结果完全不同

if (z > 0u)
{
	printf("first branch!");//实际执行的语句
}
else
{
	printf("second branch!");
}

由于这里0被当作无符号类型,所以当有符号类型和无符号类型作比较时,统一转换成无符号类型,z被转换成无符号类型后是一个很大的正数,明显大于0,因此这里就执行if后面的语句。

2、关于在Keil uVision中编译后Code, RO, RW和ZI DATA的测试

网上有很多关于这方面的讨论,它们的大致功能如下:

1、 C中的指令以及常量被编译后是RO类型数据。

2、 C中的未被初始化或初始化为0的变量编译后是ZI类型数据。

3、 C中的已被初始化成非0值的变量编译后市RW类型数据。

在我实际的测试时,各种情况如下所示:

1. 在函数内部添加一个未初始化的int类型自动变量时,例如:int a; 这时所有的值没有变化;

2. 在函数内部添加一个初始化的int类型自动变量时,例如:int a=10; Code的值增加了4,其他的没有变化;

3. 在函数内部添加一个未初始化的const修饰后的int类型变量,例如const int a; 这时所有的值都没有变化;

4. 在函数内部添加一个初始化的const修饰后的int类型变量,例如const int a=10; Code的值增加了4,其他没有变化;

5. 在函数外部添加一个未初始化的int类型的变量,例如:int a; 这时所有的值都没有变化;

6. 在函数外部添加一个初始化的int类型的变量,例如:int a=10; 这时所有的值都没有变化;

7. 在函数外部添加一个未初始化const修饰的int类型变量,例如:const int a; 这时所有的值都没有变化;

8. 在函数外部添加一个初始化const修饰的int类型变量,例如:const int a=10; 这时所有的值都没有变化;

3、让人迷惑的函数中的数组参数

有这样一个函数:

int
find_max(int array[10]) {
    int i;
    int max=array[0]; //先得到第一个元素的值
    
    for(i=1;i < 10;i+=1) //从第2个元素开始循环查找
        if (array[i] > max) //若当前元素的值大于max则直接将当前元素的值赋给max
            max=array[i];
   
    return max;//函数返回
}
这个函数有问题吗?

函数的本意是找出int类型的数组中的最大值,并且函数的参数似乎也限定了数组的长度是10,然后在函数体内就想当然的使用循环在这个数组内查找最大值。

但实际这个函数存在严重的问题。

把一个数组作为参数时指定其元素的数目是没有任何意义的,因为数组作为函数的参数在编译时实际为指向实参的指针的一份拷贝,也就是说它是一个指针,指针只知道起始地址,不可能知道数组实际的大小。若是实参刚好是一个包含10个元素的int类型数组,则函数可以正常执行,若数组的元素个数大于10,则函数在执行时就有一部分元素访问不到,而如果实参的数组元素小于10个,那就有更严重的问题,出现数组越界的危险,若是不小心修改了这些地方的数据,程序就会出现问题,因此要特别小心。

4、关于后缀++操作符与间接访问符*

有如下的代码:

/**
 @* Function name:find_char
 @* Description:find a specific char in a string array             
 @* Input:strings: the string array where we search the char
           value: the value of the char we need to search
 @* Output:0: no matched char is found, 1: we can find the char we need
 */
int
find_char(char **strings, int value) {
    assert(strings!=NULL);
     
    /* 对于列表中的每个字符串进行查找 */
    while(*strings!=NULL) {
        /* 观察字符串中的每个字符,看看它是否是我们查找的那个 */
        while(**strings!='\0') {
            if (*(*strings)++ == value) {
                return 1;
            }
        }
        strings++;
    }
     
    return 0;
}
 
/* 程序用到的实参及调用 */
char *strings[]={"A string", "Another", "Third", "Last", '\0'};
int result=find_char(strings, 'T');

注意到代码中的*(*strings)++ == value,这里为什么要加上括号呢?如果不加括号程序还会按照原来的意图执行吗?

*(*strings)++是这样解读的:

1. 括号内的先计算,*string访问字符串数组中的当前字符串(或指针);

2. 按照C语言中的操作符/运算符优先级,++和*是同级的并按照从右到左的顺序执行,所以先执行++;

3. 后缀++操作符对指针的操作实际是先产生一个指针的拷贝,这里是*string,然后再增加指针的值;

4. 最外层左边的间接访问操作符*实际是访问步骤3中产生的*string指针的拷贝(指针值未增加前的),所以其直接作用是:对当前字符串中的当前字符进行测试,看是否是我们需要的字符,由于++产生的副作用,指向当前字符串字符的指针值增加1,这个1是指向字符类型指针的每次移动的长度。

而**strings++是按照如下方式解读的(我也从网上高手请教的):

1. 按照C语言中的操作符/运算符优先级原则,++和*都是单目运算符且同级,按从右到左的顺序执行,后缀++开始执行,先产生一个strings的副本,然后指针strings移动1;

2. 前面的两个间接访问操作符实际访问的是strings的副本,得到的结果是strings[0][0],第一个字符串的第一个字符;

3. 因为strings是一个二级指针,对它执行++,实际它增加了4字节长度(32位系统),因为它保存的是一个指针的地址;

由此可以看出:执行**strings++后在内层循环中每次移动到下一个字符串的起始处,而不是移动到当前字符串中的下一个字符,与函数的本意相违背。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值