【归纳】C语言基础知识回顾 4.2 C语言与C++的不同类型转换的问题 2.C语言中反斜杠的作用

    本章归纳C语言中疑难知识点,容易出错的用法及语法,特殊用法扩展等

目录:

1.C语言运算符
1)自增自减
2)三目运算符
3)逗号表达式
4)C语言类型强制转换
2.C语言中反斜杠的作用
3.循环语句
4.补充:i++与++i的区别
5.输出格式符号的运用
6.内部函数与外部函数(static与extern)
1)内部函数:static 函数类型 函数名(函数参数表){……}
2)外部函数:[extern] 函数类型 函数名(函数参数表){……} //[]代表extern可有可无
7.关于数组的问题
7.1.字符数组的规范初始化方式
7.2.数组元素的调用(注意:在norflash中的局部变量是无法存入的,只有Nandflash启动才可以使用局部变量)


1.C语言运算符


郝斌C语言课程讲8:运算符补充,内容是:自增自减,三目运算符,逗号表达式的使用及各种运算符混合使用的优先级总结。

1)自增自减,分类:前自增: ++i;       后自增:i++;

相同处:最终都使i的值加1;

不同处:前自增整体表达式的值是 i 加 1 之后的值,后自增整体表达式的值是 i 加 1 之前的值

#include <stdio.h>
int main(void)
{
  int i, j, k, m;
  i = j = 3;
  k = i++;
  m = ++j;
  printf("i = %d, j = %d, k = %d, m = %d\n", i, j, k, m);
  ruturn 0;
}
在vc++6.0中的输出结果为: i = 4, j = 4, k = 3, m = 4

问题:为什么会出现自增?

答:1)代码更精炼    2)自增的速度更快

解析:i= i+ 1;(等价于)i+=1;内部的执行一模一样  /****/  i= i+ 1;(不等价于)i++;

学习自增时需要明白的问题:

答:1)编程时,应该尽量屏蔽掉前自增和后自增的差别 2)自增表达式最好不要作为一个更大的表达式的一部分来使用,或者说,i++和++i单独成一个语句,不要把它作为一个完整的复合语句的一部分来使用。

    例:int m = i++ + ++i + i + i++;    //这样写不但是不规范的代码,而且是不可移植的代码(该代码在不同的机器的运行结果不一样)

原因:执行(++i)时不知道(i++) 中 i 是否加1,是否立即生效,只有明晰顺序点,才能了解其是否必须在下一语句前已生效

延伸:顺序点(只有三种):逗号;   括号();  分号;

2)三目运算符: ? :  合起来叫做三目运算符

格式: A ? B : C    ⇔(等价于)
if(A)
  B;
else
  C;

例: int i = (3 > 2 ? 5 : 1);    得:i = 5;

3)逗号表达式

格式:(A, B, C, D)

功能:从左到右执行,最终表达式的值是最后一项的值

    例1:int i = (3, 2, 5, 6); 得: i = 6;

    例2:int i, j = 2; i = (j++, ++j, j+2, j-3);  得:i = 1 为什么?

/*
2018--05-20
功能:
验证逗号表达式和自增运算符的使用
*/

#include <stdio.h>
int main(void)
{
	int i, j = 2;
	i = (j++, ++j, j+2, j-3);
	printf("i = %d\n", i);

	return 0;
}
/*
在vc++6.0中的输出结果为:
i = 1
Press any key to continue
解析:1.j++ → j = 3
2.++j → j = 4
3.j+2 → j = 4(虽然表达式的值加2,但j本身并无增加)
4.j-3 → j = 1
5.i = j-3 = 1;
*/

本课程的上机总结规律为: (!) > (+, -, *, /, %) > (>, <, >=, <=, ==) > (&&, ||)

其中,(type)强制类型表达式 4级

( type-name ) cast-expression,强制表达式成为type-name指定的类型。
参考:https://baike.baidu.com/item/C%E8%AF%AD%E8%A8%80%E8%BF%90%E7%AE%97%E7%AC%A6/1539281?fr=aladdin#1_3

4) C语言类型强制转换

4.1强制类型转换是通过类型转换运算来实现的。其一般形式为: (类型说明符) (表达式)  其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。
1>强制转换变量的数据类型            2>强制转换数据的数据类型
例如: (float) a 把变量a转换为浮点型,(int)(x+y) 把x+y的结果转换为整型
注意事项:
1.类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。
2.无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。
详细参考:维基百科https://baike.baidu.com/item/C语言类型强制转换/7593367
4.2 C语言与C++的不同类型转换的问题
temp=(float)sum/total;

ave=((int)(temp+0.5)==(int)temp) ? (int)temp:int(temp+0.5); //error: syntax error before "int",

C++编译器没问题,但C语言的编译器就会出现error,主要是强制类型转换的格式不一样。

比如将int b强制类型转换为double类型,那么C语言必须:

int b = 5;
double a = (double)b;
C++是偏向面向对象的,可以用:
a = double(b);
2.C语言中反斜杠的作用

如果一行代码有很多元素,导致太长影响阅读,可以通过在结尾加\的方式,实现换行,编译时会忽略\及其后的换行符,当做一行处理。

1)在宏定义中,要换行必须使用 \ 结尾。

#define CHECK_ACTION_RETURN(expr) \
    if (!expr) { \
        printf(":failed(%d)\n", ret); \
        return ret; \
                } else { \
        printf(":ok\n"); \
                }

2)在字符串常量中,可以使用 \ 结尾。

"this \
is \
for \
testing"

和”this is for testing”是相同的,但是对于字符串写成

"this "
"is "
"for "
"testing"

效果是相同的,而且更美观。

3)另外,在普通的语句中,也可以通过 \ 实现换行,不过这时没有 \ 也是一样的效果 
比如

printf("this is for test %d %d %d\n",\
test_output_a,\
test_output_b,\
test_output_c);

printf("this is for test %d %d %d\n",
test_output_a,
test_output_b,
test_output_c);

是没有区别的,所以这时一般不会使用\。


3.循环语句

    2.1正确的条件判断表达式格式类型,如:

        for(i = 0;  表达式a; i++)            while(表达式b)        if(表达式c)

        C/C++语言表达式格式为任意格式:(按优先级排序)算数表达式>关系表达式>逻辑表达式>赋值表达式

 如果是你是硬件置位还有先后之分的,

        赋值表达式?   :=, +=, -=, *=, /=(一般不要在判断表达式中添加诸如赋值、算数运算之类的操作,增加了程序的复杂度和风险)



 if对真假逻辑的处理:if(a)--a非0为真,0为假

        常见的错误写法:

        a)i = 0; while(!(i &= (1<<2))){i ++};  则:while()内语句始终为真,进入while()语句循环

        b)i = 4; while(!(i &= (1<<2))){i ++}; 则:while()内语句为假,不循环,直接跳过while()语句

        规范写法:

        while(!(i & (1<<2))),则当i = 4时,跳出while()循环

l

#include <stdio.h>
int main(void)
{
	volatile int i = 0;
	int j = 0;
	while(!(i &= (1<<2)))		//实际被判断的是变量i的值
	{
		printf("第一循环!    ");
		printf("  %#x\n", i);
		
		if(j > 10)
			break;
		i++;
		j++;
	}
	
	printf("\n");
	i = 0x0;
	j = 0;
	while(!(i & (1<<2)))		//实际被判断的是变量i的第三位的值
	{
		printf("第二循环!  ");
		printf("  %#x\n", i);
		if(j > 10)
			break;
		i++;
		j++;
	}

	printf("\n");
	printf("我靠!\n");

	return 0;
}
/*
实验结果:

(当int i  bit2不为1时)
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	第一循环!0
	@循环陷入无限死循环:
	@ i = (0 & 4) = 0;
	@ i = i+1 = 1;
	@ i = (1 & 4) = 0;
	@ i = i + 1 = 1;
	@ ...
(当int i   bit2为1时)
	(空)

第二循环!0
第二循环!0x1
第二循环!0x2
第二循环!0x3

我靠!
Press any key to continue
总结:
(1)循环while(!(i &= (1<<2)))会改变变量i的值,把除bit2外其它位清零,如果初值i的bit2=1,则直接跳过循环;否则陷入死循环
(2)循环while(!(i & (1<<2)))不改变变量i的值
*/


4.补充:i++与++i的区别

作者:叶王
链接:https://www.zhihu.com/question/19811087/answer/80210083
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

i++ 与 ++i 的主要区别有两个:
1、 i++ 返回原来的值,++i 返回加1后的值。
2、 i++ 不能作为左值,而++i 可以。

毫无疑问大家都知道第一点(不清楚的看下下面的实现代码就了然了),我们重点说下第二点。
首先解释下什么是左值(以下两段引用自中文维基百科『右值引用』词条)。
左值是对应内存中有确定存储地址的对象的表达式的值,而右值是所有不是左值的表达式的值。
一般来说,左值是可以放到赋值符号左边的变量。但能否被赋值不是区分左值与右值的依据。比如,C++的const左值是不可赋值的;而作为临时对象的右值可能允许被赋值。左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址。
比如,
int i = 0;
int *p1 = &(++i); //正确
int *p2 = &(i++); //错误

++i = 1; //正确
i++ = 5; //错误
那么为什么『i++ 不能作为左值,而++i 可以』?
看它们各自的实现就一目了然了:
以下代码来自博客:为什么(i++)不能做左值,而(++i)可以
// 前缀形式:
int& int::operator++() //这里返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用
{//函数本身无参,意味着是在自身空间内增加1的
  *this += 1;  // 增加
  return *this;  // 取回值
}

//后缀形式:
const int int::operator++(int) //函数返回值是一个非左值型的,与前缀形式的差别所在。
{//函数带参,说明有另外的空间开辟
  int oldValue = *this;  // 取回值
  ++(*this);  // 增加
  return oldValue;  // 返回被取回的值
}
如上所示,i++ 最后返回的是一个临时变量,而临时变量是右值

5.输出格式符号的运用

/*
2018-05-07
FIle:printf格式符号
功能:
1、验证各种输出格式符号
*/
#include <stdio.h>   
/*std:    stdstandard英 [ˈstændəd]   美 [ˈstændərd]  
n.标准,规格;旗,军旗;度量衡标准;直立支柱
adj.标准的,合格的;普遍的,一般的;公认为优秀的
*/
int main(void)
{
	int i = 5;
	printf("i = %d, i =%08d, i = %8d\n", i, i, i);        
	printf("i = %02d, i =%04d, i = %06d\n", i, i, i);       
	printf("i = %010d, i =%012d, i = %014d\n", i, i, i);      

	printf("i = %#d, i =%#o, i = %#x\n", i, i, i);
	printf("i = %#d, i =%#8o, i = %#8x\n", i, i, i);
	printf("i = %#0d, i =%#08o, i = %#08x\n", i, i, i);

	return 0;
}
/*
在vc++6.0输出结果是:
i = 5, i =00000005, i =        5
i = 05, i =0005, i = 000005
i = 0000000005, i =000000000005, i = 00000000000005
i = 5, i =05, i = 0x5
i = 5, i =      05, i =      0x5
i = 5, i =00000005, i = 0x000005
Press any key to continue
*/

6.内部函数与外部函数

摘自:维基百科》static

函数分为内部函数和外部函数
当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static 函数类型 函数名(函数参数表){……}
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
外部函数
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern] 函数类型 函数名(函数参数表){……}
调用外部函数时,需要对其进行说明:
[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
[案例]外部函数应用。
⑴文件mainf.c
main()
{
extern void input(…),process(…),output(…);
input(…);
process(…);
output(…);
}
⑵文件subf1.c
……extern void input(……) /*定义外部函数*/{……}
⑶文件subf2.c
……extern void process(……) /*定义外部 函数*/{……}
⑷文件subf3.c
……extern void output(……) /*定义外部函数*/{……}静态局部变量static的存储 有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存),而且降低了程序的可读性,因此若非必要,不要多用静态局部变量。vb中语句
在过程级别中使用,用于声明变量并分配存储空间。在整个代码运行期间都能保留使用 Static 语句声明的变量的值。
static语句声明的变量,与dim语句声明的变量的主要区别是:前者只能在sub或function过程中使用,在退出sub或function过程后变量的值保留;后者使用在sub或function过程中时,退出sub或function过程后变量的值不保留。
语法Staticvarname[([subscripts])] [As [New]type] [,varname[([subscripts])] [As [New]type]] . . .
Static 语句的语法包含下面部分:
描述
varname 必需的。变量的名称;遵循标准变量命名约定。
subscripts 可选的。数组变量的维数;最多可以定义 60 维的多维数组。subscripts 参数使用下面的语法:
[lower To] upper [,[lower To] upper] . . .
如果不显式指定 lower,则数组的下界由 Option Base 语句控制。如果没有 Option Base 语句则下界为 0。
New 可选的。用它可以隐式地创建对象的关键字。如果使用 New 声明对象变量,则在第一次引用该变量时将新建该对象的实例,因此不必使用 Set 语句来对该对象引用赋值。New 关键字不能用来声明任何内部数据类型的变量,也不能用来声明从属对象的实例。
type 可选的。变量的数据类型;可以是
Byte、Boolean、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、String(对变长的字符串)、String * length(对定长的字符串)、Object、Variant、用户定义类型或对象类型。
所声明的每个变量都要有一个单独的 As type 子句。
说明
模块的代码开始运行后,使用 Static 语句声明的变量会一直保持其值,直至该模块复位或重新启动。可以在非静态的过程中使用 Static 语句显式声明只在该过程内可见,但具有与包含该过程定义的模块相同生命期的变量。
可以在过程中使用 Static 语句来声明在过程调用之间仍能保持其值的变量的数据类型。例如,下面的语句声明了一个定长的整型数组:
Static EmployeeNumber(200) As Integer
下面的语句为 worksheet 的新实例声明了一个变量:
Static X As New Worksheet
如果在定义对象变量时没有使用 New 关键字,则在使用该变量之前,必须使用 Set 语句将一个已有的对象赋给这个引用对象的变量。在被赋值之前,所声明的这个对象变量有一个特定值 Nothing,这个值表示该变量没有指向任何对象的实例。若在声明中使用了 New 关键字,则在第一次引用对象时将新建一个该对象的实例。
如果不指定数据类型或对象类型,且在模块中没有使用 Deftype 语句,则按缺省情况,定义该变量为 Variant 类型。
注意
Static 语句与 Static 关键字很相似,但是针对不同的效果来使用的。如果使用 Static 关键字(如 Static Sub CountSales ())来声明一个过程,则该过程中的所有局部变量的存储空间都只分配一次,且这些变量的值在整个程序运行期间都存在。对非静态过程而言,该过程每次被调用时都要为其变量分配存储空间,当该过程结束时都要释放其变量的存储空间。Static 语句则用来在非静态的过程中声明特定的变量,以使其在程序运行期间能保持其值。
在初始化变量时,数值变量被初始化为 0,变长的字符串被初始化为一个零长度的字符串 (""),而定长的字符串则用 0 填充。Variant 变量被初始化为 Empty。用户自定义类型的变量的每个元素作为各自独立的变量进行初始化。
注意 如果在过程中使用 Static 语句,应和其它的声明语句(如 Dim)一样将其放在过程的开始。
作用
static的作用
在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
(1)先来介绍它的第一条也是最重要的一条:隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。
下面是a.c的内容
char a = 'A'; // global variable
void msg() {
printf("Hello\n");
}
下面是main.c的内容
1
2
3
4
5
6
7
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序的运行结果是:
A Hello
你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int fun(void)
{
static int count = 10; // 此语句只在函数第一次调用时执行,后续函数调用此变量的初始值为上次调用后的值,每次调用后存储空间不释放
return count--;
},
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
(3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。不妨做个小实验验证一下。
1
2
3
4
5
6
7
8
#include <stdio.h>
int a;int main(void)
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
程序的运行结果如下
integer: 0; string: (begin)(end)
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
7.关于数组的问题

7.1.字符数组的规范初始化方式

    在C语言中,是将字符串作为字符数组来处理的。如:char c[11] = {'I', ' ', 'L', 'O', 'V', 'E', ' ', 'Y', 'O', 'U', '!' };即是用一个一维的字符数组来存放字符串“I LOVE YOU!”的,字符串中的字符是逐个存放到数组元素中的。本例中,字符串实际长度和数组长度相等。

    实际工作中,人们更关心字符串的有效长度而非字符数组的长度。

    C语言规定,以字符 '\0' 作为“字符串结束标志”。

字符数组初始化方法:

1)初始化列表,把各个元素依次赋值给数组中的各元素。如:char c[11] = {'I', ' ', 'L', 'O', 'V', 'E', ' ', 'Y', 'O', 'U', '!' };

    长度11字节;

2)自定义。    char c[ ] = {'I', ' ', 'L', 'O', 'V', 'E', ' ', 'Y', 'O', 'U', '!' };

3)用字符串常量初始化字符数组,例:char c[ ] = {"I LOVE YOU!"};    或    char c[ ] = "I LOVE YOU!";

    注意:此法数组c的长度为12字节,末尾系统自动加 '\0' 空字符。

    说明:字符数组并不要求他的最后一个字符为 '\0' ,甚至可以不包含'\0' ,合法。但为求处理方法一致,应人工于字符数组末人为加上'\0' 空字符。 

正确字符串初始化方式:

char * a = "0123456789abcdef\n";

char * a = "Hello world!\n";

错误字符串初始化方式:

int a = "Hello world!\n";

7.2 数组元素的调用(注意:在norflash中的局部变量是无法存入的,只有Nandflash启动才可以使用局部变量

26行代码可被识别:

.

31行代码可被识别:


31行代码可被识别:



       


猜你喜欢

转载自blog.csdn.net/weixin_39420903/article/details/80142696