1.&是位运算(按位与),同时也是取地址符。&&是逻辑运算,逻辑与。
2.*是乘法运算(a*b),也是指针运算符(*p)。
3.反斜杠\同时具有接续符和转义符的作用。
编译器会将反斜杠剔除,跟在反斜杠后面的字符自动解到前一行。在接续单词时,反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格。接续符适合在定义宏代码块时使用。
当反斜杠作为接续符使用时可直接出现在程序中,当反斜杠作为转义符使用时需出现在字符或字符串中。
4.注释/**/。编译器会在编译过程删除注释,但不是简单的删除而是用空格代替 in/**/t a,报错编译器认为双引号括起来内容都是字符串,双斜杠也不例外。“/*……*/”型注释不能被嵌套。
5.#运算符用于在预编译期将宏参数转换为字符串
##运算符用于在预编译期粘连两个符号
6.格式化输入输出
%(flags)(width)(.prec)type
以中括号括起来的参数为选择性参数, 而%与type则是必要的。底下先介绍type的几种形式整数
%d 整数的参数会被转成一有符号的十进制数字
%u 整数的参数会被转成一无符号的十进制数字
%o 整数的参数会被转成一无符号的八进制数字
%x 整数的参数会被转成一无符号的十六进制数字, 并以小写abcdef表示
%X 整数的参数会被转成一无符号的十六进制数字, 并以大写ABCDEF表示浮点型数
%f double 型的参数会被转成十进制数字, 并取到小数点以下六位, 四舍五入。
%e double型的参数以指数形式打印, 有一个数字会在小数点前, 六位数字在小数点后, 而在指数部分会以小写的e来表示。
%E 与%e作用相同, 唯一区别是指数部分将以大写的E 来表示。
%g double 型的参数会自动选择以%f 或%e 的格式来打印, 其标准是根据欲打印的数值及所设置的有效位数来决定。
%G 与%g 作用相同, 唯一区别在以指数形态打印时会选择%E 格式。
%c 整型数的参数会被转成unsigned char型打印出。
%s 指向字符串的参数会被逐字输出, 直到出现NULL字符为止
%p 如果是参数是"void *"型指针则使用十六进制格式显示。
prec 有几种情况
1). 正整数的最小位数。
2).在浮点型数中代表小数位数
3).在%g 格式代表有效位数的最大值。
4).在%s格式代表字符串的最大长度。
5).若为×符号则代表下个参数值为最大长度。
width为参数的最小长度, 若此栏并非数值, 而是*符号, 则表示以下一个参数当做参数长度,例:sscanf(buf, "%*s%s", dns);
flags 有下列几种情况
+ 一般在打印负数时, printf()会加印一个负号, 整数则不加任何负号。此旗标会使得在打印正数前多一个正号(+)。
# 此旗标会根据其后转换字符的不同而有不同含义。当在类型为o 之前(如%#o), 则会在打印八进制数值前多印一个o。
而在类型为x 之前(%#x)则会在打印十六进制数前多印’0x’, 在型态为e、E、f、g或G 之前则会强迫数值打印小数点。在类型为g 或G之前时则同时保留小数点及小数位数末尾的零。
0当有指定参数时, 无数字的参数将补上0。默认是关闭此旗标, 所以一般会打印出空白字符。
%10d -- 输出整型,十进制,占10位。 long int, short int 都可以用
%08x -- 按16进制无符号输出,小写,共8 位,不足8位左边添0
%20u -- 按十进制无符号数输出,占20位
%lf--double
h代表输入短整型数据
7.要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234; 那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
*((void (*)( ))0x100000 ) ( );
首先要将0x100000强制转换成函数指针,即:
(void (*)())0x100000
然后再调用它:
*((void (*)())0x100000)();
用typedef可以看得更直观些:
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000)();
8.短路规则:
||从左向右开始计算,当遇到为真的条件时停止计算,整个表达式为真;所有条件为假时表达式才为假。
&&从左向右开始计算,当遇到为假的条件时停止计算,整个表达式为假;所有条件为真时表达式才为真。
9.C语言隐式类型转换
算术运算式中,低类型转换为高类型
赋值表达式中,表达式的值转换为左边变量的类型
函数调用时,实参转换为形参的类型
函数返回值,return表达式转换为返回值类型
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) puts("> 6") : puts("<= 6");
}
10.指针和数组的对比
数组声明时编译器自动分配一片连续内存空间,指针声明时只分配了用于容纳指针的4字节空间
在作为函数参数时,数组参数和指针参数等价
数组名在多数情况可以看做常量指针,其值不能改变
指针的本质是变量,保存的值被看做内存中的地址
11.各种进制之间的转换
O<==>B(8<==>2)
二进制数与八进制数之间的转换 转换方法是:以小数点为界,分别向左右每三位二进制数合成一位八进制数,或每一位八进制数展成三位二进制数,不足三位者补0。例如: (423.45)8=(100 010 011.100 101)2 (1001001.1101)2=(001 001 001.110 100)2=(111.64)8
D<==>B(10<==>2)
整数和小数分别转换。整数除以2,商继续除以2,得到0为止,将余数逆序排列。
22 / 2 11 余0
11/2 5 余 1
5 /2 2 余 1
2 /2 1 余 0
1 /2 0 余 1
所以22的二进制是10110小数乘以2,取整,小数部分继续乘以2,取整,得到小数部分0为止,将整数顺序排列。0.8125x2=1.625 取整1,小数部分是0.6250.625x2=1.25 取整1,小数部分是0.250.25x2=0.5 取整0,小数部分是0.50.5x2=1.0 取整1,小数部分是0,结束所以0.8125的二进制是0.1101十进制22.8125等于二进制10110.1101
H<==>B(16<==>2)
转换方法:以小数点为界,分别向左右每四位二进制合成一位十六进制数,或每一位十六进制数展成四位二进制数,不足四位者补0。例如: (ABCD.EF)16=(1010 1011 1100 1101.1110 1111)2 (101101101001011.01101)2=(0101 1011 0100 1011.0110 1000)2=(5B4B.68)16
12.测试大小端(Little-endian和Big-endian模式)
32bit宽的数0x12345678
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
内存地址 存放内容
0x4000 0x12
0x4001 0x34
0x4002 0x56
0x4003 0x78
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。
内存地址 存放内容
0x4000 0x78
0x4001 0x56
0x4002 0x34
0x4003 0x12
union xxx{
int a;
char b;
}kkk;
int checkCPU()
{
kkk.a=12345678;
return kkk.b;
}
void main()
{
printf("%d\n",checkCPU());//78
}
13.传值和传址
#include <stdio.h>
#include <stdlib.h>
void getmemory(char *p)
{
p=(char *) malloc(100);
strcpy(p,"hello world");
}
int main( )
{
char *str=NULL;
getmemory(str);
printf("%s\n",str);
free(str);
return 0;
}
程序崩溃,getmemory中的malloc 不能返回动态内存,free()对str操作很危险,getmemory中p是形参,是一个指针变量,getmemory(str)调用后,传入的是指针变量保存的对象地址,p=(char *) malloc(100)实际上是把申请的动态内存空间的首地址付给p指向的地址(即str指向的地址null)这个是错误的。应该修改成指向指针的指针 void getmemory(char **p),这样malloc返回的地址付给*p(即str变量本身)。正确的应该改为如下:
#include <stdio.h>
#include <stdlib.h>
void getmemory(char **p)
{
*p=(char *) malloc(100);
strcpy(*p,"world");
}
int main( )
{
char *str=NULL;
getmemory(&str);
printf("%s\n",str);
free(str);
return 0;
}
14.数组的地址
void main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
输出:2,5
*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
&a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int)
int *ptr=(int *)(&a+1);
则ptr实际是&(a[5]),也就是a+5
原因如下:
&a是数组指针,其类型为 int (*)[5]。而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同。a是长度为5的int数组指针,所以要加 5*sizeof(int),所以ptr实际是a[5],但是prt与(&a+1)类型是不一样的(这点很重要),所以prt-1只会减去sizeof(int*)
a,&a的地址是一样的,但意思不一样
a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址,
a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].
15.强制类型转换
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *p1 = (int*)(&a + 1);
int *p2 = (int *)((int)a + 1);
printf("0x%x, 0x%x\n", p1[-1], *p2);
return 0;
}
变量a的内存地址为:0x0034fc50 问printf输出值是多少
首先分析p1
&a为数组指针,类型是"int (*)[5]",数组指针是指向数组首元素的地址的指针,&a的内存地址为:0x0034fc50,&a + 1指针加一,向后移动一个元素,这里的元素类型是"int (*)[5]",其空间大小为20字节,因此,&a + 1的内存地址为:0x0034fc64(0x0034fc50 + 0x14),p1的内存地址为:0x0034fc64 ,p1[-1]数组下标为负数,向前移动一个元素(注:数组越界在编译时与运行时都不会报错,除非操作非法内存),p1[-1]元素所在的地址为:0x0034fc60,因此,可知p1[-1]的地址为a + 4的地址p1[-1] == *(a + 4) == 5
继续分析p2
(int)a的值为0x0034fc50 ,(int)a + 1等于0x0034fc51
分析内存结构,测试环境字节序为little endian
01 00 00 00 02 00 00 00 03 00 0...
*p2等于0x2000000
16.可变参数列表
float average(int n, ...)
{
va_list args;
int i = 0;
float sum = 0;
va_start(args, n);
for(i=0; i<n; i++)
{
sum += va_arg(args, int);
}
va_end(args);
return sum / n;
}
average(5, 1, 2, 3, 4, 5);
17.宏函数实现的debug
#ifdef DEBUG
#define debug(fmt,args...) printf (fmt ,##args)
#define debugX(level,fmt,args...) if (DEBUG>=level) printf(fmt,##args);
#else
#define debug(fmt,args...)
#define debugX(level,fmt,args...)
#endif /* DEBUG */
18.strcpy
char * strcpy( char *strDest, const char *strSrc )
{
assert( (strDest != NULL) && (strSrc != NULL) );
char *address = strDest;
while( (*strDest++ = * strSrc++) != ‘\0’ );
return address;
}
19.strcat
char * strcat (char * dst, const char * src)
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to end of dst */
return( dst ); /* return dst */
}
20.strcmp
int strcmp(const char *s1, const char *s2)
{
while (*s1 == *s2)
{
if (*s1 == 0)
return 0;
s1++;
s2++;
}
return *(unsigned const char *)s1-*(unsigned const char *)s2;
}
21.memcpy
void memcpy(void* pvTo, void* pvFrom, size_t size)
{
void* pbTo = (byte*)pvTo;
void* pbFrom = (byte*)pvFrom;
ASSERT(pvTo != NULL && pvFrom != NULL); //检查输入指针的有效性
ASSERT(pbTo>=pbFrom+size || pbFrom>=pbTo+size);//检查两个指针指向的内存是否重叠
while(size-->0)
*pbTo++ == *pbFrom++;
return(pvTo);
}
22.实现字符串倒序显示
int invertedOrderStr()
{
char* src = "hello,world";
int len = strlen(src);
char* dest = (char*)malloc(len+1);//要为\0分配一个空间
char* d = dest;
char* s = &src[len-1];//指向最后一个字符
while( len-- != 0 )
*d++=*s--;
*d = 0;//尾部要加\0
printf("%s\n",dest);
free(dest);// 使用完,应当释放空间,以免造成内存汇泄露
return 0;
}
23.二分查找算法
int BSearch(elemtype a[],keytype key,int n)
{
int low,high,mid;
low=0;high=n-1;
while(low<=high)
{
mid=(low+high)/2;
if(a[mid].key==key) return mid;
else if(a[mid].key<key) low=mid+1;
else high=mid-1;
}
return 0;
}
24.冒泡排序
void BubbleSort(elemtype x[],int n) //时间复杂度为0(n*n);
{
int i,j;
elemtype temp;
for(i=1;i<n;i++)
{
for(j=0;j<n-i;j++)
{
if(x[j].key>x[j+1].key)
{
temp=x[j];
x[j]=x[j+1];
x[j+1]=temp;
}
}
}
}
25.位操作
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
26.怎么判断链表中是否有环?
int testLinkRing(Link *head)
{
Link *t1=head,*t2=head;
while( t1->next && t2->next)
{
t1 = t1->next;
if (NULL == (t2 = t2->next->next))
return 0;//无环
if (t1 == t2)
return 1;
}
return 0;
}