一、注释符号
C语言中的符号:
奖项:Best of show
功能:处理三种文件格式(PGM、PPM和ASCII Art)的降采样工具
作者:Google华裔工程师Don Hsi-Yun Yang
注释符号,下面哪些注释是正确的。
#include <stdio.h>int main()
{
int/*...*/i; // 注释由空格来代替,所以是正确的
char* s = "abcdefgh //hijklmn"; // 在双引号中的注释会当成字符,所以也不会报错,但与程序愿意不符
//Is it a \ valid comment? // ‘\’接续符,整个语句就是一个注释
in/*...*/t i; //会报错
return 0;
}
gcc编译后出错结果:
[root@localhost 1]# gcc 1-1.c
1-1.c: In function ‘main’:1-1.
c:11: error: ‘in’ undeclared (first use in this function)1-1.
c:11: error: (Each undeclared identifier is reported only once1-1.
c:11: error: for each function it appears in.)1-1.
c:11: error: expected ‘;’ before ‘t’
注释规则小结:
1、编译器会在编译过程删除注释,但不是简单的删除而是用空格代替
2、编译器认为双引号括起来内容都是字符串,双斜杠也不例外
3、"/*........*/"型注释不能被嵌套
4、你觉得y = x/*p是什么意思?
作者本意:把x除以*p的结果赋值给y
编译器:将/*作为一段注释的开始,把/*后的内容都当成注释内容,直到*/出现为止
在编译器看来,注释和其它程序元素都是平等的,所以,作为程序员也不能看轻注释。
5、出色注释怎么来写
1、注释应该准确易懂,防止二义性,错误的注释有害而无利
2、注释是对代码的提示,避免臃肿和喧宾夺主
3、一目了然的代码避免加注释
4、不要用缩写来注释代码,这样可能会产生误解
5、注释用于阐述原因而不是用于描述程序的运行过程
下面是一个漂亮程序的截取部分:
/*
========================================================================
FILE: Form.c
SERVICES:
GENERAL DESCRIPTION: Concrete implementation of RootForm and base IForm
methods
========================================================================
========================================================================
Copyright ?1999-2005 QUALCOMM Incorporated
All Rights Reserved.
QUALCOMM Proprietary/GTDR
========================================================================
========================================================================
*/
/*==================================================================================
XXXXXXX Confidential Proprietary
(c) Copyright XXXXXXX - All Rights Reserved
Revision History:
Modification
Author Date CR Number Major Changes
---------------------- ------------ ------------ ----------------------------
Daniel Rossler 01/18/2007 LIBkk94550 Add check for NULL pointers
in order to avoid a panic
==================================================================================*/
// If there are still forms on the stack, then we need to set up several things:
// 1. The topmost form is the active form
// 2. All other forms are not active
// 3. The topmost form is being listened to via mlFormActive
// 4. The topmost non-popup form is being listened to via mlFormTopmostNonPopup
// 5. The topmost non-popup form and all popup forms on top of it are shown
// 6. Forms below the topmost non-popup form are now shown
二、接续符和转义符
下面是C语言中程序:
#include <stdio.h>
#def\
ine MAX\
255
int main()
{
/\
/这是\
\
注释
i\
n\
t\
*\
p\
= \
NULL;
printf("%0X\n", p);
return 0;
}
接续符的使用:
1、编译器会将反斜杠剔除,跟在反斜杠后面的字符自动解到前一行
2、在接续单词时,反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格
3、接续符适合在定义宏代码块时使用
下面是一个宏代码块的使用实例:
#include <stdio.h>
#define SWAP(a,b) \ // 使用define的原因就是
{ \ // define定义宏块必须要在一行内完成
int temp = a; \
a = b; \
b = temp; \
}
/*
void swap(in a, int b)
{
int tmp = a;
a = b;
b = tmp;
}*/
// 如果使用这个函数,不能实现a与b的替换,
// 因为这里进行的是参数的值传递,它实现的是a和b拷贝的实现
int main()
{
int a = 1;
int b = 2;
SWAP(a,b);
printf("a=%d, b=%d\n", a, b);
return 0;
}
宏块的使用实际就是代码块的替换。
C语言中的转义符(\)主要用于表示无回显字符(实际代表一个动作),也可用于表示常规字符
\n 回车换行 \t 横向挑到下一制表位置 \v 竖向挑格\b 退格\r 回车\f 走纸换页\\ 反斜杠“\”\' 单引号\a 鸣铃
\ddd 1-3位八进制数所代表的字符 \xhh 1-2位十六进制数所代表的字符
小结:
1、C语言中的反斜杠(\)同时具有接续符和转义符的作用
2、当反斜杠作为接续符使用时可直接出现在程序中
3、当反斜杠作为转义符使用时需出现在字符或字符串中
三、单引号和双引号
下面是一个实例:
#include <stdio.h>
int main()
{
//char* p1 = 1 ; //p指向内存地址为1
//char* p2 = '1'; //字符在内存中实际是一个整数,它指向地址为49的内存
char* p3 = "1";
//printf("%s, %s, %s", p1, p2, p3);
printf('\n'); //printf的原型函数为int printf(char const *format, ... );所以理由同
//char *p2 = '1'; \n 就是10;出现段错误
printf("\n");
return 0;
}
1、C语言中的单引号用来表示字符常量
2、C语言中的双引号用来表示字符串常量
'a'表示字符常量
在内存中占1个字节
‘a’+1表示‘a’的ASCII码加1,结果为‘b’
“a”表示字符串常量
在内存中占2个字节
“a” + 1表示指针运算,结果指向“a”结束符‘\0’
混淆概念的代码:
#include <stdio.h>
int main()
{
//char c = " ";//将字符串赋值给一个字符,字符串实际代表一段包含字符串的
//内存地址,而将它赋值给char,会被截断,发生无法预料的结果
char c = '';
// while( c=="\t" || c==" " || c=="\n" )
while( c=='\t' || c==' ' || c='\n' )
{
scanf("%c", &c);
}
return 0;
}
小结:
1、本质上单引号括起来的一个字符代表整数
2、双引号括起来的字符代表一个指针
3、C编辑器接受字符和字符串的比较,可意义是错误的
4、C编译器允许字符串对字符变量赋值,其意义是可笑的
清晰基本概念,远离低级错误
四、逻辑运算符
逻辑运算符的短路问题分析代码:
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
if( ++i > 0 || ++j > 0 )
{
printf("%d\n", i);
printf("%d\n", j);
}
return 0;
}
#include <stdio.h>
int g = 0;
int f()
{
return g++;
}
int main()
{
if( f() && f() ) //第一个f()为假,后++导致返回为0,而后加加。短路了。
{
printf("%d\n", g);
}
printf("%d\n", g);
return 0;
}
1、程序中的短路
短路规则:
》||从左向右开始计算,当遇到真的条件时停止计算,整个表达式为真;所有条件为假时表达式才为假。
》&&从左向右开始计算,当遇到假的条件时停止计算,整个表达式为假;所有条件为真时表达式才为真。
2、“!”到底是神马?
#include <stdio.h>
int main()
{
printf("%d\n", !0);
printf("%d\n", !1);
printf("%d\n", !100);
printf("%d\n", !-1000);
return 0;
}
C语言中的逻辑符“!”只认得0,只知道见了0就返回1.因此当其作用的值不为0时,就返回0,可见其对0的钟爱。
3、三目运算符(a ? b : c)可以作为逻辑运算符的载体
规则:当a的值为真时,返回b的值;否则返回c的值
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 0;
c = a < b ? a : b;
//(a < b ? a : b) = 3; //返回为a的右值,不能用3进行赋值
*(a < b ? &a : &b) = 3; //返回a的地址,使用指针可以实现赋值操作
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
return 0;
}
五、位运算符分析
在C语言中的位运算往往用于外设的操作,通过串口和并口去操作外设。
乘除余和加减的优先级都要高于位运算符
& 按位与 | 按位或^ 按位异或~ 取反<<左移>>右移
位运算符符合交换律和结合律
1、C语言中为什么还需要运算符?
位运算往往运行速度快,这对于我们的嵌入式设备来说是非常重要的。
2、左移和右移
# 左移运算符<<将运算数的二进制位左移
规则:高位丢弃,低位补0
# 右移运算符>>把运算数的二进制位右移
规则:高位补符号位,地位丢弃
3、0x1 << 2+3的值会是什么?
先算2+3,结果为32
4、位运算符分析
防错准则:
1、避免位运算符,逻辑运算符和数学运算符同时出现在一个表达式中。
2、当位运算符,逻辑运算符和数学运算符需要同时参与运算时,尽量使用括号()来表达计算次序
小技巧:
# 左移n位相当于乘以2的n次方,但效率比数学运算符高
# 右移n位相当于除以2的n次方,但效率比数学运算符高
下面是一个使用不同方式实现交换操作
#include <stdio.h>
// 要使用一个临时变量,多使用了一个资源
#define SWAP1(a,b) \
{ \
int temp = a; \
a = b; \
b = temp; \
}
//进行了改进,但a+b有可能溢出。它适用于所有类型
#define SWAP2(a,b) \
{ \
a = a + b; \
b = a - b; \
a = a - b; \
}
//这是一个神奇的方法,避免了溢出。但只用于int型,float不可以
#define SWAP3(a,b) \
{ \
a = a ^ b; \
b = a ^ b; \
a = a ^ b; \
}
int main()
{
int a = 1;
int b = 2;
SWAP1(a,b);
SWAP2(a,b);
SWAP3(a,b);
return 0;
}
面试题详解:
/*
有一个数列,其中的自然数都是以偶数次形式出现,只有
一个自然数出现的次数为奇数次。编写程序找出这个自然数
*/
问题的解决方案:
A:
1、将数列排序
2、遍历数列并且计数
缺点:时间复杂度高,也就是说耗时。
B:
1、遍历数列找到最大值max
2、动态申请max+1的int型数组b
3、把b数组的每个元素清零
4、for循环
{
b[a[i]]++;
}
5、for遍历b数组,寻找奇数元素,就是奇数次出现的自然数,此自然数就等于其所在的数组下标号。
缺点:空间换时间,秏空间比较大。
C:就是下代码实现。使用位运算来实现查找奇数次自然数
#include <stdio.h>
#define DIM(a)(sizeof(a)/sizeof(*a))
int main()
{
int a[] = {2, 3, 5, 7, 2, 2, 2, 3, 5, 7, 1, 1, 1};
int find = 0;
int i = 1;
for(i = 0; i < DIM(a); i++)
{
find = find ^ a[i]; //通过数列所有数的异或操作,最后只剩余了出现奇数次的元素
}
printf("find = %d", find);
return 0;
}
课后思考:
1、&&,||,!与&,|,~ 的意义是否相同?它们可以再条件表达式中交替使用吗?为什么?
答:它们是不同的。&&,||,!是对数据整体来操作,而&,|,~ 是对数据按位来操作的,所以它们意义不同,最终结果也不同。因此不可以在条件表达式
中相互替换。
2、1<<32的结果是什么?1<<-1的结果是什么?为什么?
当使用1<<32的时候,程序会出现警告:
[Warning] left shift count >= width of type [enabled by default]
当使用1<<-1的时候,程序也会出现警告:
[Warning] left shift count is negative [enabled by default]
个人解释:
我们在进行位移操作时一定要注意:
1、移动位数保证非负
2、移动位数不能超过类型宽度
六、++,--操作符使用分析
1、一对令人头疼的兄弟
int i = 3;
(++i) + (++i) + (++i);
上面这个表达式的值是不确定的,因为这是C语言的一段灰色地带,对于不同的编译器会有不同的编译方式,也就对应着不同结果。
一共有三种编译方式:
1、按从左到右的顺序依次来计算,即4+5+6
2、先从左边计算前两个的++,后依次计算。即5+5+6(5+5+6+7...)
3、先计算所有++,而后再相加,即6+6+6
gcc和g++采用的是第二种方式。
2、令人头疼的逗号表达式
逗号表达式不是C语言的灰色地带,它有自己明确的规则:从左到右的顺序求值,最后一个表达式的值就是逗号表达式的值
int x;
int i = 3;
x = (++i, i++, i+10);
运行结果为x = 15;
笔试面试中的++i+++i+++i
上式的阅读要使用标准的阅读方法,贪心法
1、编译器处理的每个符号应该尽可能多的包含字符
2、编译器以从左向右的顺序一个一个尽可能多的读入字符
3、当即将读入的字符不可能和已读入的字符组成合法符号为止(而使用空格可以阻止贪心阅读,所以我们在编写代码应该实时的使用空格)
下面是一个实例:
#include <stdio.h>
int main()
{
int i = 0;
//int j = ++i+++i+++i;
//上式使用贪心法阅读到++i++ 等效于2++ ,显然出现了错误
int a = 1;
int b = 2;
int c = a+++b;
printf("%d\n", c); // c的值为3
printf("%d\n", a); //a的值为2
int* p = &a;
b = b/*p; //报错了,这里成了注释,空格可以终止贪心,b = b / *p;
return 0;
}
六、优先级和类型转换的分析
下面是一个运算符优先级的实例:
#include <stdio.h>
#include <malloc.h>
typedef struct _demo
{
int* pInt;
float f;
} Demo;
int func(int v, int m)
{
return (v & m != 0); //==和!=优先级大于位操作符
}
int main()
{
Demo* pD = (Demo*)malloc(sizeof(Demo));
int *p[5]; //[]优先级高于* ,指针数组
int *f(); //()优先级高于* ,指针函数
int i = 0;
i = 1, 2; //所有运算符','优先级最低
//*pD.f = 0; //.运算符优先级高于* ,所以会报错
pD->f = 0;
free(pD);
return 0;
}
C语言隐式类型转换:
1、算术运算式中,低类型转换为高类型
2、赋值表达式中,表达式的值转换为左边变量的类型
3、函数调用时,实参转换为形参的类型
4、函数返回值,return表达式转换为返回值类型
代码分析:
#include <stdio.h>
int main()
{
char c = 'c';
short s = 0;
s = c;
printf("%d\n", sizeof(s+c)); //结果为4
return 0;
}
#include <stdio.h>
int main()
{
int i = -2;
unsigned int j = 1;
if( (i + j) >= 0 )
{
printf("i+j>=0\n");
}
else
{
printf("i+j<0\n");
}
printf("i+j=%0x\n", i + j);
printf("i+j=%d\n", i+j);
return 0;
}
运行结果为:
i+j>=0
i+j = ffffffff
i+j = -1
说明 -1 的补码就是 ffffffff ,因为负数在计算机中是以补码的形式存储的。