c的小技巧和基础知识
基础知识
1. 十进制的ASCII码
- 0~9数字对应十进制48-57
- a~z字母对应的十进制 十六进制61-7A
- A~Z字母对应的十进制65-90 十六进制41-5A
- 汉语的ASCII码与英文数字不同,占两个字节,且为了便于区分每一个字节的第一个符号位为1,转化成10进制则是负数
2. c语言里合法的标识符
C语言规定,标识符以字母或下划线开头,后跟若干字母,下划线或数字,大小写字母组成的标识符是不同的,标识符的长度没有限制.例如,以下标识符是合法的:a,x,x3,BOOK_1,sum5.
3. 输入与输出格式
整数与浮点数
对比代码如下
int a = 3; float b = 2; int c = a/b; printf("%d",c);
结果为1
int a = 3; float b = 2; float c = a/b; printf("%d",c);
结果为0
- double在接受输入时必须是lf,否则值为0,输出可以是f。
int a = 3; float b = 2; float c = a/b; printf("%f",c);
结果为 1.500000
- 综上 输入和输出格式最好一致
输出格式
%xd,右对齐,一共占x数字位,原数字所占位数大于x位按原来的数字位算,小于x位按x位算
%-xd,左对齐
%0xd,大于x位同上,小于x位多余位数用0补齐(可用于“三位数反转”)。
换行(tips)
- 每五个数一行,if count %5==0
+(tips)
使x=x+x
printf("%d=%d",i,a[0]); for(n=1;n<=k;n++) printf("+%d",a[n]); printf("\n");
4. boolean
单个整数值可以表示真假,0为假,其他数值为真。
5. 运算符
运算符包含算术运算符,关系运算符(<>高于==和!=),逻辑运算符,条件运算符,赋值运算符,优先值递减。
- c的逻辑运算符采用短路策略。
- 条件运算符(?:)是C语言中唯一具的三目运算符,就是说它有三个运算对象。
- 例如:(a>b)?a+b:a-b
其中,如果a=2,b=1,那么a>b成立,执行a+b这个表达式,运算结果为3;但如果a=2,b=3,那么a>b不成立,那么执行a-b这个表达式,运算结果为-1.
6. switch
switch(x){
case x1:;break;//当x是x1的时候则执行
case x2:;break;
default:;
7. goto
goto x;
x:
8. swap
void swap(int *a, int *b){
int temp ;
temp = *a;
*a = *b;
*b = temp;
}
9. 调用头文件
#include “test.h” (自己的项目内)
#include <stdio.h>
10. 宏定义
#define exchange(a,b) { int t;t=a;a=b;b=t;}//注意放在一行里//定义函数
#define FALSE 0
#define SQ(x) (x)*(x)
#define LAG >
综合运用:
#include<stdio.h>
#define MAX
#define MAXIMUM(x,y) (x>y)?x:y
#define MINIMUM(x,y) (x>y)?y:x
int main()
{
int a=10,b=20;
#ifdef MAX //如果max被define则执行下面一句,否则执行else后面的一句
printf("更大的数字是 %d\n",MAXIMUM(a,b));
#else
printf("更小的数字是 %d\n",MINIMUM(a,b));
#endif//每次必加
#ifndef MIN//如果min没被define则执行下面一句,
printf("更小的数字是 %d\n",MINIMUM(a,b));
#else
printf("更大的数字是 %d\n",MAXIMUM(a,b));
#endif
#undef MAX//取消max的define
#ifdef MAX
printf("更大的数字是 %d\n",MAXIMUM(a,b));
#else
printf("更小的数字是 %d\n",MINIMUM(a,b));
#endif
#define MIN//define min
#ifndef MIN
printf("更小的数字是 %d\n",MINIMUM(a,b));
#else
printf("更大的数字是 %d\n",MAXIMUM(a,b));
#endif
return 0;
}
以上实例输出结果为:
更大的数字是 20
更小的数字是 10
更小的数字是 10
更大的数字是 20
11. 静态变量
修饰变量,变量又分为局部和全局变量,但他们都存在内存的静态区
静态区。所以即使这个函数运行结束,这个静态变量的值不会被销毁,函数下次使用时仍能使用。
静态全局变量
作用于仅限于变量被定义的文件。其他文件即使用extern声明也没法使用,准确说就是作用域是从定义处开始,到文件结束, 在定义处之前的那些代码不能使用它。
静态局部变量
static局部变量中文名叫静态局部变量。
它与普通的局部变量比起来有如下几个区别:
- 位置:静态局部变量被编译器放在全局存储区.data,所以它虽然是局部的,但是在程序的整个生命周期中存在。
- 访问权限:静态局部变量只能被其作用域内的变量或函数访问。 也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。
- 静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。
这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。
代码
第一种:
#include<stdio.h>
int main()
{
void fun();
for(int i=0;i<3;i++)
fun();
return 0;
}
void fun()
{
int i=0;
static int static_i=0;
printf("i=%d\n",i);
printf("static_i=%d\n",static_i);
i++;
static_i++;
以上实例输出结果为:
i=0(不变)
static_i=0(静态可以变)
i=0
static_i=1
i=0
static_i=2
注:但局部静态不能使main里的变量改变
第二种:
#include <stdio.h>
int main()
{
int i,num;
num=2;
for(i=0;i<3;i++)
{
printf("num 变量为 %d \n",num);
num++;
{
static int num=1;//只进行一次初始化
printf("内置模块 num 变量为 %d\n",num);
num++;
}
}
return 0;
}
以上实例输出结果为:
num 变量为 2
内置模块 num 变量为 1
num 变量为 3
内置模块 num 变量为 2
num 变量为 4
内置模块 num 变量为 3
此外:全局变量只初始化一次 不管是static 还是extern
全局变量加static 是为了限制其作用域( 仅在本文件中有效 在其他文件中不可见)
补充
(auto int)实际上是int(auto 可省略)
在C语言中使用auto关键字声明一个变量为自动变量,是C语言中应用最广泛的一种类型,
在函数内定义变量时,如果没有被声明为其他类型的变量都是自动变量,也就是说,省去类型说明符auto的都是自动变量。
这里的其他类型指的是变量的存储类型即:静态类型变量(static )、寄存器类型变量(register)和外部类型变量(extern)。
12. 学会按位与(&)按位或(|)按位异或(^)
按位与
原理
0&0=0; 0&1=0; 1&0=0; 1&1=1
代码
#include <stdio.h>
int main()
{
int a,b;
a=077;
b=a&3;
printf("a & b(decimal) 为 %d \n",b);
b&=7;
printf("a & b(decimal) 为 %d \n",b);
return 0;
}
以上实例输出结果为:
a & b(decimal) 为 3
a & b(decimal) 为 3
代码解释
0开头是8进制数, 077 = 8*7+7=63, 其2进制是 111111
3的2进制 编码是 011
2者作“按位与”运算后,结果是 011,所以是3;
而77的二进制是 1001101, 和3(011)作“按位与”运算后, 是 001, 所以是1
按位或(|)
0|0=0; 0|1=1; 1|0=1; 1|1=1
按位异或(^)
0^0=0; 0^1=1; 1^0=1; 1^1=0
左移运算<<
比如0的二进制为00……0000000
那么0就为11……11111110<<4就是将~0的二进制码向左移动4位,右边补0,
而
即得11……1110000
~按位取反
直接计算公式**~a=-(a+1)**;(适用于正数和负数)
原理:
转化成二进制,每位取反,0变1,1变00 = ~(0000 0000)2 = (1111 1111)2=255a=-(a+1);
但是javascript默认是有符号的(-127到+126),所以要求补码(反码+1)再转为负数
(1111 1111)的补码是(0000 0001)符号位取反=(1000 0001)=-1
转到最后其实就是原码+1再转为负数,跟上面的公式是一样的
补码(机器里储存的码)
正数
正整数的补码是其二进制表示,与原码相同 。
负数
求负整数的补码,将其对应正数二进制表示所有位取反(包括符号位,0变1,1变0)后加1
【不算正负号,加起来等于十进制】
负数首位为1,正数首位为0.
正数:先取反,首位不变再取反后再加1【即加1后变为负数】【取反后(负数)的补码】
~1 = ~(0000 0001)= (1111 1110) = (1000 0010) = -2
负数: 首位不变取反加1再取反【补码取反】
~-1=-1是这样表示的:-1的绝对值是1,二进制0000 0001,取反为1111 1110,加1等于1111 1111,所以-1为1111 1111,这个取反当然是0000 0000
不管正负,都是+1后变为符号相反的数。
综合运用
题目:取一个整数a从右端开始的4~7位。
程序分析:可以这样考虑:
(1)先使a右移4位。
(2)设置一个低4位全为1,其余全为0的数。可用(0<<4)(就是将上一步得到的二进制码(11……1110000)取反,
得00……0001111 )
(3)将上面二者进行&运算。
程序源代码:
#include <stdio.h>
int main()
{
unsigned a,b,c,d;
printf("请输入整数:\n");
scanf("%o",&a);
b=a>>4;
c=~(~0<<4);//小套路
d=b&c;
printf("%o\n%o\n",a,d);
return 0;
}
以上实例输出结果为:
请输入整数:
36 36(八进制) = 11110(二进制)
36
1
小技巧
1. ACM
- 只允许进行算法相关的操作,不允许进行访问网络,画图等操作。
- 以回车结尾,行首不应有空格,输出的每两个数或字符串之间应以单个空格隔开。
- 由于程序不能直接读取键盘和屏幕,不允许使用getch(),getche(),gotoxy(),clrscr(),conio.h
- 输出格式很严格,不允许多字符或少字符,不需要界面友好分。(出现presentation error可能离成功只有一步之遥)
- 用double代替float,可能会出现谜之wrong
- 如果出现超时和超内存将会分别返回
Time Limit Exceeded
和Memory Limit Exceeded
错误信息,如果程序执行时出现错误,比如非法指针,数组越界等,将会返回Runtime Error
信息。如果你的程序没有出现上面的信息,说明程序顺利执行结束了。接下来,就是对你的程序的输出也就是运行结果进行检查,如果你的执行结果和我们的标准答案完全一样,则返回Accepted
,也就说明你这个题目做对了。如果除去空格,换行,tab外完全相同,则说明你的代码格式错误,将返回Presentation Error
,如果你输出的内容有一部分和标准答案完全一样,但是还输出了一些其他内容,则说明你多输出了,这时候将返回Output Limit Exceeded
错误信息,出现其他情况,就说明你的输出结果和标准答案不一样,就是Wrong Answer
了
2. 声明
- 尽量用const xx声明常数。
3. 菜鸟c示例4的改进:
题目
输入某年某月某日,判断这一天是这一年的第几天?
代码
#include <stdio.h>
int main()
{
int day,month,year,sum,leap;
printf("\n请输入年、月、日,格式为:年,月,日(2015,12,10)\n");
scanf("%d,%d,%d",&year,&month,&day); // 格式为:2015,12,10
switch(month) // 先计算某月以前月份的总天数
{
case 1:sum=0;break;
case 2:sum=31;break;
case 3:sum=59;break;
case 4:sum=90;break;
case 5:sum=120;break;
case 6:sum=151;break;
case 7:sum=181;break;
case 8:sum=212;break;
case 9:sum=243;break;
case 10:sum=273;break;
case 11:sum=304;break;
case 12:sum=334;break;
default:printf("data error");break;
}
sum=sum+day; // 再加上某天的天数
if(year%400==0||(year%4==0&&year%100!=0)) {// 判断是不是闰年
leap=1;
} else {
leap=0;
}
if(leap==1&&month>2) { // *如果是闰年且月份大于2,总天数应该加一天
sum++;
}
printf("这是这一年的第 %d 天。",sum);
printf("\n");
}
注意点
- year%400==0||(year%100!=0&&year%4==0)[只需一个else if 不需要goto]
- 如果是闰年的处理方法:闰年只是总日期加1天,则if(flag ==1&&month>2)sum++;
- 总天数不需要用到for循环,只需要在switch时就进行sum
- month的天数可用数组记录
4. continue的用法
- 排除法
5. 三目运算符的用途
- max = a>b ? a : b ;的功能是取a和b中的大值,并把这个值赋给变量max。
- max=a>b ? a>c ? a : c : b>c ? b : c ;相当于 a>b ? (a>c ? a : c) : (b>c ? b : c)
6. 求最大公因数和最小公倍数
用处
减小时间复杂度
示例
被除数 / 除数 = 商 …… 余数
6497 / 3869 = 1 …… 2628
3869 / 2628 = 1 …… 1241
2628 / 1241 = 2 …… 146
1241 / 146 = 8 …… 73
146 / 73 = 2 …… 0
因此最大公约数为:73
最小公倍数=两数之积/最大公约数=6497*3869/73=25136893
方法
辗转相除法
if(a<b){
t=b;b=a;a=t;//调整除数和被除数的位置
}
r=a%b;
int n=a*b;
while(r!=0)
{
a=b;
b=r;
r=a%b;
}
printf("%d",b);
补充:辗转相除法就是用来求最大公因数的,不能直接用来求最小公倍数。但是利用二者的关系,可以很方便的求出最小公倍数。
7. 素数
判断素数时不需要用flag标记是否为素数,若提前退出,j必小于i,所以if以j = i为条件,成立则为素数。
8. 后面两个数是前三个数之和
不用数组的方法
方法一
f1=1;
f2=1;
while(){
printf("%d",f1);
f1=f1+f2;
f2=f1+f2;
}
方法二
f1 = 0;
f2 = 1;
while(){
sum =f1+f2;
printf("%d",sum);
f1 = f2;
f2 = sum;
}
9. 代替三重循环
#include<stdio.h>
int main()
{
int s=0,a,n,t;
printf("请输入 a 和 n:\n");
scanf("%d%d",&a,&n);
t=a;
while(n>0)
{
s+=t;
a=a*10;
t+=a;
n--;
}
printf("a+aa+...=%d\n",s);
return 0;
}
10. 判断回文字符
判断是否是回文数,先将此数字按逆序重排,再与原先数值比较:
#include <stdio.h>
int main( )
{
int x;
int sum = 0;
printf("请输入一个整数:\n");
scanf("%d", &x);
for (int i = x; i > 0; i /= 10)
sum = sum * 10 + i % 10;//亮点
if(sum == x)
printf("%d 是回文数\n",x);
else
printf("%d 不是回文数\n", x);
}
11. 调用函数进行阶乘的两种方法
第一种:
int step;
int sum = 1;
void dfs(int step){
sum *= step;
if(step==1){
printf("%d",sum);
return;
}
dfs(step - 1);
return;
}
int main(){
dfs(5);
return 0;
}
第二种:
#include <stdio.h>
int main()
{
int i;
int fact(int);//函数声明
for(i=0;i<6;i++){
printf("%d!=%d\n",i,fact(i));
}
}
int fact(int j)
{
int sum;
if(j==0){
sum=1;
} else {
sum=j*fact(j-1);
}
return sum;
}
12. 快速排序
- 快速排序要先从右向左检索
13. 判断一个数是否为质数
思路
- 质数的定义:只有1和它本身两个因数的自然数,叫质数(或称素数)。
(如:由2÷1=2,2÷2=1,可知2的因数只有1和它本身2这两个约数,所以2就是质数。
与之相对立的是合数(除了1和它本身两个因数外,还有其它因数的数,叫合数。)- 根据质数的定义,在判断一个数n是否是质数时,只要用1至n-1去除n,看看能否整除即可。
- 还有更好的办法:先找一个数m,使m的平方大于n,再用小于等于m的质数去除n(n为被除数),如果都不能整除,则n必然是质数。
- 如我们要判断1993是不是质数,50*50>1993,那么只要用1993除以<50的质数看是否能整除,若不能即为质数。在100内共有25个质数。
14. 将一个数组变为逆序
#include<stdio.h>
#define N 10
int main()
{
int a[N]={0,1,2,3,4,5,6,7,8,9};
int i,t;
printf("原始数组是:\n");
for(i=0;i<N;i++)
printf("%d ",a[i]);
for(i=0;i<N/2;i++)
{
t=a[i]; //亮点
a[i]=a[N-1-i];
a[N-1-i]=t;
}
printf("\n排序后的数组:\n");
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
15. 将一个数按要求插入数组
方法一(逆序)
#include<stdio.h>
int main()
{
int opo[11]={1,4,6,9,13,16,19,28,40,100};//原始的数组,题目给的
int a,b,c,e;
printf("这是原始的数组");
for(a=0;a<10;a++)printf("%d ",opo[a]);
printf("\n输入一个数,插入原数组");
scanf("%d",&b);
for(c=9;c>=0;c--){ //使用逆序的话,可以省掉很多过程,而且清晰
if(b<opo[c]) {
opo[c+1]=opo[c];
}
else {
opo[c+1]=b;break;
}
if(c==0) opo[0]=b; //因为是逆序,如果b比第一个小的话写不进去,要特意弄个if
}//哇,逆序超简洁,惊了
for(e=0;e<=10;e++)
printf("%d ",opo[e]);
}
方法二
#include<stdio.h>
int main()
{
int a[11]={1,4,6,9,13,16,19,28,40,100};
int temp1,temp2,number,end,i,j;
printf("原始数组是:\n");
for(i=0;i<10;i++)
printf("%4d",a[i]);
printf("\n插入一个新的数字: ");
scanf("%d",&number);
end=a[9];
if(number>end)
a[10]=number;
else
{
for(i=0;i<10;i++)
{
if(a[i]>number)
{
temp1=a[i];
a[i]=number;
for(j=i+1;j<11;j++)
{
temp2=a[j];
a[j]=temp1;
temp1=temp2;
}
break;
}
}
}
for(i=0;i<11;i++)
printf("%4d",a[i]);
printf("\n");
return 0
16. 求字符串长度
代码
int length(char *s) //传入指针(数组)
{
int i=0;
while(*s!='\0')
{
i++;
s++; //当前位置加上*s类型的长度
}
return i;
}
17. 滚动数组
void move(int array[],int n,int offset)
{
int *p,*arr_end;
arr_end=array+n; //数组最后一个元素的下一个位置
int last;
//滚动直到偏移量为0
while(offset)
{
last=*(arr_end-1);
for(p=arr_end-1;p!=array;--p) //向右滚动一位
*p=*(p-1);
*array=last;
--offset;
}
}