当前位置:首页 » 编程语言 » c语言程序装单体号能不能举报
扩展阅读
webinf下怎么引入js 2023-08-31 21:54:13
堡垒机怎么打开web 2023-08-31 21:54:11

c语言程序装单体号能不能举报

发布时间: 2022-04-25 16:56:50

1. c语言程序设计:通讯录程序设计

#include "stdlib.h"
#include "string.h"
#include "conio.h"
#include "stdio.h"
#include "dos.h"
FILE *fp;
int i; //i是全局变量 可代替length
struct address
{ char postnum[10];
char a[40]; //家庭地址
};
struct birthday
{ int year;
int month;
int day;
};
struct ffriend
{ int num; //序号
char name[20];
char sex;
char telephone[13];
struct birthday birthday;
struct address address;

}
friends[50];

void Load()
{
int j;
long k;
fp=fopen("friend.txt","a+"); //打开文件friend.txt
if(fp!=NULL)
{
for(i=1;i<50;i++)

{
j=fgetc(fp);
if(j==EOF) //判断是否到了文件尾
return;
k=i-1;
fseek(fp,k*sizeof(struct ffriend),SEEK_SET);
fread(&friends[i],sizeof(struct ffriend),1,fp); //从文件中读取一条记录
}

}
else
{
fp=fopen("friend.txt","w");
i=1;
}

}
void Show(int j)
{
// friends[j].num=i;
printf("\n\n\t编号-Nnumber: %3d",friends[j].num);
printf("\n\t姓名-Name:%-20s",friends[j].name);
printf("\n\t性别-Sex:%c",friends[j].sex);
printf("\n\t联系电话-Telephone:%-13s",friends[j].telephone);
printf("\n\t出生日期-Birthday:%4d-%2d-%3d",friends[j].birthday.year,friends[j].birthday.month,friends[j].birthday.day);
printf("\n\t邮编-Postnum:%-10s",friends[j].address.postnum);
printf("\n\t通信地址-address:%-40s",friends[j].address.a);
}
void Append(int j)
{
fflush(stdin); //区内清除文件缓冲区,文件以写方式打开时将缓冲容写入文件
//stdin是一个标准FILE*(文件型指针)指向通常是用键盘的输入的输入流
friends[j].num=j;
printf("\n\t\t\t序号-Number:%d",j);
printf("\n\t\t\t姓名-Name:");
scanf("%s",friends[j].name);
fflush(stdin); //为什么没有fflush(stdin); 的效果会自动跳过呢?
printf("\t\t\t性别-Sex(m/w):"); //为什么输入汉字男女也会自动跳过联系电话呢
scanf("%c",&friends[j].sex);
printf("\t\t\t联系电话-telephone:");
scanf("%s",friends[j].telephone);
printf("\t出生日期-birthday");
printf("\n\t\t\t年份-year:");
scanf("%d",&friends[j].birthday.year);
printf("\t\t\t月份-month:");
scanf("%d",&friends[j].birthday.month);
printf("\t\t\t日-day:");
scanf("%d",&friends[j].birthday.day);
printf("\t\t\t邮编-Postnumber:");
scanf("%s",friends[j].address.postnum);
printf("\t\t\t通信地址-Address:");
scanf("%s",friends[j].address.a);
getchar();
}
void Delete()
{
int k;
printf("\n\tDelete 序号-Number:");
scanf("%d",&k);
if(k<=i)
{
for(int j=k;j<i+1;j++) /*插入位置后的元素顺序后移*/
{
strcpy(friends[j].name,friends[j+1].name); /*交换元素内容*/
friends[j].sex=friends[j+1].sex;
strcpy(friends[j].telephone,friends[j+1].telephone);
friends[j].birthday.year=friends[j+1].birthday.year;
friends[j].birthday.month=friends[j+1].birthday.month;
friends[j].birthday.day=friends[j+1].birthday.day;
strcpy(friends[j].address.postnum,friends[j+1].address.postnum);
strcpy(friends[j].address.a,friends[j+1].address.a);

}
i--;
}
else
{
printf("输入的序号太大!");
}
}
void Modify(int j)
{
Append(j);
}
void Save()
{
int j;
fp=fopen("friend.txt","w");
for(j=1;j<=i;j++)
{
fwrite(&friends[j],sizeof(struct ffriend),1,fp);
}
fclose(fp);

}

void main()
{
int j;
char grade;
char searchname[10];
Load();
i--;
do
{
printf("\t\t\t\t简易通讯录\n\n");
printf("功能选择(Function choose)\n");
printf("\1A.读取(Read)\n");
printf("\2B.增添(Append)\n");
printf("\6C.插入(Insert)\n");
printf("\5D.删除(Delete)\n");
printf("\5E.查询(Search)\n");
printf("\6F.修改(Modify)\n");
printf("\2G.保存(Save)\n");
printf("\1H.退出(Quit)\n");
printf("请选择(Choice)\n注:输入A~H的大写字母\n");
scanf("%c",&grade);
switch(grade)
//加个大写和小写 都可以啊
{
case 'A': j=1; //显示功能
while(getchar()!=0x1b&&j<=i) //增添按Esc键退出
{
Show(j++);
printf("\n请按回车键继续!");
}
if(j-1<1)
{
printf("\n\t空文档,无任何记录-Empty Note\n");
printf("\n请按回车键继续!");
getchar();

}
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'B': //增加功能
while(i<=50) //增加按ESC键退出的功能
{
i++;
Append(i);
printf("\t是否继续增加?y/n");
//修改 加上else if 其他就跳出或强制打印出出错
char a=getchar();
if(a=='n'||a=='N')
break;
}
if(i==51)
printf("\n\t文档已满,无法增加记录-note full");
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
getchar();
break;
case 'C':
int k;
printf("\n\t输入要插入的位置:");
scanf("%d",&k);
j=i+1;
friends[j].num=j;
for(j;j>k;j--) //插入功能
{
strcpy(friends[j].name,friends[j-1].name); //数据后移
friends[j].sex=friends[j-1].sex;
strcpy(friends[j].telephone,friends[j-1].telephone);
friends[j].birthday.year=friends[j-1].birthday.year;
friends[j].birthday.month=friends[j-1].birthday.month;
friends[j].birthday.day=friends[j-1].birthday.day;
strcpy(friends[j].address.postnum,friends[j-1].address.postnum);
strcpy(friends[j].address.a,friends[j-1].address.a);

}
Append(k);
i++;
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'D': //删除功能 //增添随意删除多条记录的功能
Delete();
if(i<1)
printf("\n没有记录-No records\n");
printf("\n请按回车键继续!");
getchar();
getchar();
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'E': //查询功能
printf("\n\t请输入要查询记录的相关姓名:"); //增添查询按列号的
scanf("%s",searchname);
for(j=1;j<=i;j++)
{
if(strcmp(searchname,friends[j].name)==0) //比较字符串
{
Show(j);
break;
}
}
if(i<1)
{
printf("\n 没有您所查询的记录-No records");
printf("\n 请按回车键继续!");
getchar();
}
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
getchar();
break;
case 'F': //修改功能 / /增添按序列号查询
printf("\n\t请输入要修改记录的相关姓名:");
gets(searchname);
scanf("%s",searchname);
for(j=1;j<=i;j++)
{
if(strcmp(searchname,friends[j].name)==0) //比较字符串
{
Modify(j);
// getchar(); //加个输出确定修改吗
}
}
if(i<1) //修改功能
{ printf("\n 没有您所要修改的记录-No records");
printf("\n 请按回车键继续!");
getchar();
getchar();
}
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'G':
Save(); //存盘功能
getchar();
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;

case 'H': //退出功能
char x;
printf("是否保存?yes/no\n");
scanf("%s",&x);
//getchar();
// getchar();
if(x=='y')
{
Save();
exit(0);
}
else
exit(0);

default:
printf("\n\t输入有误,请输入正确的序列号!");
printf("\n请按回车键继续!");
getchar();
printf("\n\t\t\t\t\t\t\t此次操作结束");
printf("\n\t------------------------------------------------------------\t\t\n");
}
}while(1);
}

2. 将C语言里的源程序画流程图, 流程图里的语言有严格要求么 比如乘号 除号怎么写, 还有C语言源程

流程图是程序员之间交流的工具,并不能交给计算机自动转换成程序,没有特别严格的规范。
你可以用Visio画流程图。
就写 a % 2 == 0 就可以了,也可以写 a mod 2 == 0,反正是给人看的。
矩形框里可以写多个过程,甚至包括概括性的描述,但是注意是从上而下顺序执行的。

3. c源程序不能表示的数制是

c源程序不能表示的数制是:二进制。

C语言源程序不能表示二进制,在C语言中,所有的数值型常量都带有符号,所以整型数量只区别整型和长整型两种形式,整型常量和长整型常量均可用十进制、八进制、十六进制3种形式表示。

在程序运行过程中,其值不能被改变的量称为常量。常量也分为整型、实型、字符型等。以上是常量所具有的类型属性,这些类型决定了各种常量所占存储空间的大小和数的表示范围。在C程序中,常量是直接以自身的存在形式体现其值和类型。

(3)c语言程序装单体号能不能举报扩展阅读

二进制对计算机的重要性:

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。

当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。【计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0】

20世纪被称作第三次科技革命的重要标志之一的计算机的发明与应用,因为数字计算机只能识别和处理由‘0’.‘1’符号串组成的代码。其运算模式正是二进制。

19世纪爱尔兰逻辑学家乔治布尔对逻辑命题的思考过程转化为对符号"0''.''1''的某种代数演算,二进制是逢2进位的进位制。0、1是基本算符。因为它只使用0、1两个数字符号,非常简单方便,易于用电子方式实现。

4. c语言编程 不能和百度一样

部分 1: 准备工作
1
下载和安装编译器。 C语言需要通过编译器解释为计算机可以理解的机器码。 编译器通常是免费的, 不同的操作系统上一般使用不同的编译器。
对于Windows系统, 可以尝试 Microsoft Visual Studio Express 或者MinGW。
对于Mac系统, XCode是一款优秀的C语言编译器。
对于Linux, gcc是一个不错的选择。

2
理解基本概念。 C语言是一门古老的语言, 但却十分强大。它最初是为Unix操作系统设计的, 但后来被移植到了几乎所有的操作系统上,并得到了很多扩展。C语言的现代版本是C++。
C语言本质上是由函数构成的,在函数体中你可以使用变量,条件语句,循环等语句来存储和处理数据。

3

查看一些基本的代码。 下面是用C语言写的一段非常基本的代码, 阅读这些代码,尝试理解这种语言的不同部分是如何工作的, 并对程序的运作原理有初步的认识。 "

#include <stdio.h>
int main()
{
printf("Hello, World!\n");
getchar();
return 0;}

"[1]
这里的#include 指令在程序开始之前就出现了, 它的功能是把包含有你需要的函数的库加载进来。 在这个例子中,引入stdio。h 使得我们能够使用 printf() 和 getchar() 这两个函数。
这里的 main() 指令会告诉编译器,程序需要运行一个叫做“main”的函数,该函数运行完毕后返回一个整数值。所有的C语言都要运行一个“main”函数。
{} 符号表示括号内的所有内容都是函数的一部分。在本例中,他们标记了所有的内容都是“main”函数的一部分。
printf() 函数能够将小括号中的内容显示在用户的屏幕上。双引号保证了这个字符完全按照字面的样子输出, \n 组合告诉编译器这串字符输出完之后将光标移动到下一行 。
; 符号表示一行的结束。绝大部分C代码都以分号结束。
getchar()函数告诉编译器这段程序要等待一个按键的输入才能够继续。考虑到大部分编译器在运行完程序之后会立刻关掉程序窗口,这个功能还是很有用的,因为这样可以让程序保持运行直到有键被按下才会结束。
return 0 指令表示程序的结束。请注意”main”函数是一个int类型的函数,也就是说当函数结束时需要返回一个整数。如果返回0则表示程序正确的执行了,其他数字表示程序运行时发生了错误。

4
尝试编译这段程序。 把上面的代码输入到你的代码编辑器中,然后保存为”*。c”文件。 用你的编译器编译它, 一般来说点击Build或Run按钮即可。
5
要养成写注释的习惯。 注释是代码中的一部分,它不会被编译, 但是却可以告诉你代码做了些什么。这对于提醒你自己你的代码是干什么的以及让其他开发者理解你的代码都很有帮助。
在C语言中添加注释只需要把要注释的部分的前面添加/*, 后面添加 */。
不要吝啬你的注释,除了特别简单明了的地方都尽量加上注释吧。
注释功能也可以快速的屏蔽一部分代码但不删除它们。只需要给你想要排除的代码用注释标签包起来它们就不会被编译。如果你想要改回来,去掉注释标签即可。

END
部分 2: 变量的使用
1
理解变量的功能。 变量是用来存储数据的,不管是计算得出的还是用户输入的数据。变量在使用前要先定义,并且有不同的类型可以选择。
有以下几种常见的变量类型 int, char, 和 float。 每种变量类型都代表一种数据存储的格式。
学习声明变量。 变量在使用前要先被创建出来,或者叫”声明”。 声明一个变量只需要在变量类型的后面写出变量的名字即可。比如,下面就是一些变量声明的例子: "

float x;
char name;
int a, b, c, d;

"
注意,你可以在一行中声明多个变量,只要它们的类型是一样的就行,你只需用逗号把变量名隔开即可。
和大多数的C代码一样,变量的声明也要以分号结尾。

理解在何处声明变量。 变量的声明必须要放在每个代码块之前(代码块是指用大括号{}包起来的一段代码)。如果你在代码块后边声明变量,程序就不能正确执行了。
用变量来存储用户的输入。 现在你了解到了变量的一些基本原理, 你可以写一段简单的程序来存储用户的输入。这次你需要用到另外一个叫scanf的函数, 它的功能是把用户的输入赋值给指定的变量。 "

#include <stdio。h>
int main()
{
int x;
printf( "请输入一个数: " );
scanf( "%d", &x );
printf( "你输入了: %d", x );
getchar();
return 0;
}

"
这里的"%d"符号告诉scanf函数在用户的输入中找出整数。
x前面的&符号告诉scanf在哪里找到要修改的变量,并把输入的整数值存进去。
最后的printf命令读出输入的整数并返回给用户。

处理变量。 你可以用数学表达式来处理之前存储的变量。需要注意一个重要的差别:在数学表达式中单个=是赋值号,作用是把等号右边的值赋给等号左边的变量, 而==则是比较两个变量是否相等。 "

x = 3 * 4; /* 把x设为3*4,也就是12 */
x = x + 3; /* 把x的值增加3,然后把新的值赋值给x */
x == 15; /* 检查x是否等于15 */
x < 10; /* 检查x是否小于10 */

"

END
部分 3: 使用条件语句
1
理解条件语句的基本概念。大多数程序都是由条件语句驱动的, 这样的语句可以判断一个条件是TRUE(真)还是FALSE(假), 然后据此执行不同的动作。最基本的条件语句是if语句。
C语言中的TRUE和FALSE和你平常理解的有点不太一样。TRUE和任何非0的数总是相等的。当你执行一个比较时,如果结果是TRUE,会返回一个”1”。如果结果是FALSE,会返回0。弄清楚这一点能帮助你更好的理解IF语句的执行过程。

2
学习基本的比较符号。条件语句是以比较大小的数学表达式为核心的。下面列出了最常用的一些比较符号: "

> /* 大于 */
< /* 小于 */
>= /* 大于或等于 */
<= /* 小于或等于 */
== /* 等于 */
!= /* 不等于 */

"
"

10 > 5 TRUE
6 < 15 TRUE
8 >= 8 TRUE
4 <= 8 TRUE
3 == 3 TRUE
4 != 5 TRUE

"

3
写一个IF语句。 利用IF语句,你可以根据一个表达式计算的结果决定之后的程序如何运行。之后学习了其他条件语句后你可以把它们组合起来实现更强大的功能,不过现在写一段简单的代码熟悉一下就行了。 "

#include <stdio.h>

int main()
{
if ( 3 < 5 )
printf( "3比5小");
getchar();
}

"

4
使用ELSE/ELSE IF语句来扩展你的条件判断。 在IF语句中你可以添加ELSE 和ELSE IF语句来处理更多不同的结果。 ELSE后面的语句在IF中的判断结果为FALSE时执行。 ELSE IF则可以让你在一个代码块中使用多个IF语句来处理更多的情况。阅读下面的代码看一下他们是怎么工作的。 "

#include <stdio.h>

int main()

{
int age;

printf( "请输入您的年龄: " );

scanf( "%d", $age );

if ( age <= 12 ) {

printf( "你是个孩子!\n" );

}

else if ( age < 20 ) {

printf( "年轻的感觉真好!\n" );

}

else if ( age < 40 ) {

printf( "你充满了青春的活力!\n" );

}

else {

printf( "充满智慧的年纪! \n" );

}

return 0;

}
"[2]

这段代码接收用户输入的一个数据然后传递给IF语句。如果这个数据满足第一个条件,则第一个printf被执行。如果没有满足第一个条件,则后面的各个ELSE IF会逐个进行判断直到有一个满足条件的分支为止。如果没有任何分支满足条件,则ELSE语句被执行。

END
部分 4: 学习循环语句
1
理解循环的原理。 循环是编程中很重要的一部分, 它们让你可以重复执行一段代码直到满足特定条件为止。这个机制使你可以很容易的实现重复的动作,同时省去了每次做条件判断的麻烦。
有3种类型的循环:FOR, WHILE, 和 DO…WHILE。

2
使用FOR循环。这是最常见和好用的循环类型。它会不断的运行循环内的函数直到循环条件不再成立。FOR循环需要包含3条语句:初始化变量,循环条件,和变量更新的方式。如果你不需要其中的某个语句,把该处空着打一个分号即可,否则的话循环会无限运行。[3] "

#include <stdio.h>
int main()
{
int y;
for ( y = 0; y < 15; y++;){
printf( "%d\n", y );
}
getchar();
}

"
在上面的程序中,y被设为0,循环继续运行的条件是y小于15。每次循环中y的值被打印出来,并且被增加1。一旦y=15,循环就结束了。

3
使用WHILE循环。WHILE循环比FOR循环要简单的多。它们只有一个语句,只要该语句为TRUE循环就不断执行。你不需要初始化或更新变量,不过你可以在循环体中做这些事。 "

#include <stdio.h>
int main()
{
int y;
while ( y <= 15 ){
printf( "%d\n", y );
y++;
}
getchar();
}
"

这个循环每执行一次,y++命令就把y的值增加1。一旦y达到16,循环就结束了。(记住只有在y小于等于15的条件下循环才会执行。)

4
使用DO…WHILE 循环。这种循环在你想要确保一个循环至少要被执行一次时非常管用。在FOR和WHILE循环中,循环条件的检测是在循环开始之前进行的,这也就意味着有可能第一次检测就无法通过,那样的话循环体一次都不会被执行。然而DO。。。WHILE循环会先执行一次循环体然后再做检测,这就保证了循环体至少会被执行一次。 "

#include <stdio.h>
int main()
{
int y;
y = 5;
do {
printf("循环被执行!\n");
} while ( y != 5 );
getchar();
}

"

在上面的循环中,即使循环条件检测的结果为FALSE还是会展示一条信息。变量y的值被设为5而WHILE循环被设置为只有当y 不等于5时才运行,所以循环执行到条件检测时就会终止。但信息还是被展示出来了,因为条件检测是在输出信息之后的。
DO…WHILE循环中的WHILE语句必须以;结尾。这是唯一一种循环体以分号结尾的情形。

END
部分 5: 使用函数
1
理解函数的基本原理。 函数是可以被程序的其他部分调用的自成一体的代码块。使用函数可使你更容易重复一段代码,同时也让程序变得简单易读、便于修改。函数中可以包含前面提到的所有技术,甚至可以包含其他函数。
前面的例子中的main()就是一个函数,同样getchar()也是。
要想写出高效且易读的代码,函数是至关重要的。用好函数可以使你的程序条理更清晰。

2
从函数原型开始。在真正开始编写一个函数之前,你最好先搞清楚你要完成什么功能,并从函数原型开始编写。函数的基本语法格式为: “返回值类型 函数名 (参数1, 参数2, …);”。 比如下面是一个把两个数相加的函数: "

int add ( int x, int y );

"
上面的代码创建了一个把输入的x和y相加然后返回他们的和的函数。

3
把函数添加到程序中。你可以用上面的函数原型实现一个把用户输入的两个数相加的函数。下面的程序展示了"add"函数是如何处理输入的数字的。 "

#include <stdio。h>
int add ( int x, int y );
int main()
{
int x;
int y;
printf( "请输入要求和的两个数: " );
scanf( "%d", &x );
scanf( "%d", &y );
printf( "您输入的数字之和为 %d\n" add( x, y ) );
getchar();
}
int add ( int x , int y )
{
return x + y;
}

"
请注意,函数的原型也需要放在程序的顶部,这样能保证当这个函数被调用时编译器已经知道存在这个函数,同时也知道它的返回类型。不过只有你想在函数调用处之后再实现这个函数时才有必要这么做。如果你直接把add()函数的实现放在main()函数之前,那么即使不声明函数原型也是一样的。
这个函数的实现代码其实是放在程序的底部的。main() 函数获取了用户输入的两个整数并把他们传给add()函数以便后者进行处理,然后add()函数把计算的结果返回给main() 。
当add()函数被定义之后,你就可以在程序中的任何地方调用它了。

END
部分 6: 不断学习
找一些C语言编程相关的书来看。 这篇指南涵盖了C语言中最基础的部分,但对于完整的C语言只是体系来说这只是皮毛。如果能有一本好的参考书你在学习C语言的道路上能省去许多麻烦

加入一些社区。不论是在线上还是线下,都有一些很棒的致力于学习和发展优秀编程语言的社区。如果能找到一些志同道合的C语言程序员,并和他们相互交流, 你一定能进步的很快。
如果可能的话还可以尝试黑客马拉松活动。在这项活动参赛的团体或个人需要在有限的时间里对给出的问题提出自己的程序和解决方案,因此很能培养人的创造力。你还可以籍此认识许多优秀的程序员。并且世界各地都有规律性举办的黑客马拉松活动。

参加一些课程。虽然你没必要重新回到学校修得计算机科学的学位,但是适当的参加一些相关课程还是会让你的学习过程有质的飞跃。没有什么能比一位C语言专家的言传身教更能帮助你了。通常你总能在网络上找到一些培训课程,也有一些专业的计算机培训机构可供选择。还有一些大学的优秀课程是免费对外开放的,你可以去旁听。

考虑学习C++。 如果你已经掌握了C语言,了解一下C++将对你大有裨益。因为C++是C语言更现代的版本, 它更加的灵活和方便。C++是以面向对象的思想设计的,掌握C++之后你就可以在几乎所有操作系统中编写强大的程序了。

5. C语言与JAVA编写程序的语言可以互用吗

不可以!但是两者的算法思想是一样的!但两者的语法略有区别,所以不能直接用!

6. 请问,可以用C语言编写测试程序,来测试java用Swing开发的软件吗

要看你怎么测试了
单体测试还是系统测试

如果做单体测试,还是用Junit吧

如果是系统测试,就人工测试

7. C语言,哪位好心的大哥,姐姐:能告述我位运算吗我看不懂啊!

位运算简介及实用技巧(一):基础篇
什么是位运算?
程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算说穿了,就是直接对整数在内存中的二进制位进行操作。比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and运算。举个例子,6的二进制是110,11的二进制是1011,那么6 and 11的结果就是2,它是二进制对应位进行逻辑运算的结果(0表示False,1表示True,空位都当0处理):
110
AND 1011
---------------
0010 --> 2
由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。当然有人会说,这个快了有什么用,计算6 and 11没有什么实际意义啊。这一系列的文章就将告诉你,位运算到底可以干什么,有些什么经典应用,以及如何用位运算优化你的程序。
Pascal和C中的位运算符号
下面的a和b都是整数类型,则:
C语言 | Pascal语言
-------+-------------
a & b | a and b
a | b | a or b
a ^ b | a xor b
~a | not a
a << b| a shl b
a >> b | a shr b
注意C中的逻辑运算和位运算符号是不同的。520|1314=1834,但520||1314=1,因为逻辑运算时520和1314都相当于True。同样的,!a和~a也是有区别的。
各种位运算的使用
=== 1. and运算 ===
and运算通常用于二进制取位操作,例如一个数 and 1的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数.
=== 2. or运算 ===
or运算通常用于二进制特定位上的无条件赋值,例如一个数or 1的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0,对这个数or 1之后再减一就可以了,其实际意义就是把这个数强行变成最接近的偶数。
=== 3. xor运算 ===
xor运算通常用于对二进制的特定一位进行取反操作,因为异或可以这样定义:0和1异或0都不变,异或1则取反。
xor运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即(a xor b) xor b = a。xor运算可以用于简单的加密,比如我想对我MM说1314520,但怕别人知道,于是双方约定拿我的生日19880516作为密钥。1314520 xor 19880516 = 20665500,我就把20665500告诉MM。MM再次计算20665500 xor 19880516的值,得到1314520,于是她就明白了我的企图。
下面我们看另外一个东西。定义两个符号#和@(我怎么找不到那个圈里有个叉的字符),这两个符号互为逆运算,也就是说(x # y) @ y = x。现在依次执行下面三条命令,结果是什么?
x <- x # y
y <- x @ y
x <- x @ y
执行了第一句后x变成了x # y。那么第二句实质就是y <- x # y @ y,由于#和@互为逆运算,那么此时的y变成了原来的x。第三句中x实际上被赋值为(x # y) @ x,如果#运算具有交换律,那么赋值后x就变成最初的y了。这三句话的结果是,x和y的位置互换了。
加法和减法互为逆运算,并且加法满足交换律。把#换成+,把@换成-,我们可以写出一个不需要临时变量的swap过程(Pascal)。
procere swap(var a,b:longint);
begin
a:=a + b;
b:=a - b;
a:=a - b;
end;
好了,刚才不是说xor的逆运算是它本身吗?于是我们就有了一个看起来非常诡异的swap过程:
procere swap(var a,b:longint);
begin
a:=a xor b;
b:=a xor b;
a:=a xor b;
end;
=== 4. not运算 ===
not运算的定义是把内存中的0和1全部取反。使用not运算时要格外小心,你需要注意整数类型有没有符号。如果not的对象是无符号整数(不能表示负数),那么得到的值就是它与该类型上界的差,因为无符号类型的数是用00到$FFFF依次表示的。下面的两个程序(仅语言不同)均返回65435。
var
a:word;
begin
a:=100;
a:=not a;
writeln(a);
end.
#include <stdio.h>
int main()
{
unsigned short a=100;
a = ~a;
printf( "%d\n", a );
return 0;
}
如果not的对象是有符号的整数,情况就不一样了,稍后我们会在“整数类型的储存”小节中提到。
=== 5. shl运算 ===
a shl b就表示把a转为二进制后左移b位(在后面添b个0)。例如100的二进制为1100100,而110010000转成十进制是400,那么100 shl 2 = 400。可以看出,a shl b的值实际上就是a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。
通常认为a shl 1比a * 2更快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。
定义一些常量可能会用到shl运算。你可以方便地用1 shl 16 - 1来表示65535。很多算法和数据结构要求数据规模必须是2的幂,此时可以用shl来定义Max_N等常量。
=== 6. shr运算 ===
和shl相似,a shr b表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)。我们也经常用shr 1来代替div 2,比如二分查找、堆的插入操作等等。想办法用shr代替除法运算可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运算,效率可以提高60%。
位运算的简单应用
有时我们的程序需要一个规模不大的Hash表来记录状态。比如,做数独时我们需要27个Hash表来统计每一行、每一列和每一个小九宫格里已经有哪些数了。此时,我们可以用27个小于2^9的整数进行记录。例如,一个只填了2和5的小九宫格就用数字18表示(二进制为000010010),而某一行的状态为511则表示这一行已经填满。需要改变状态时我们不需要把这个数转成二进制修改后再转回去,而是直接进行位操作。在搜索时,把状态表示成整数可以更好地进行判重等操作。这道题是在搜索中使用位运算加速的经典例子。以后我们会看到更多的例子。
下面列举了一些常见的二进制位的变换操作。
功能 | 示例 | 位运算
----------------------+---------------------------+--------------------
去掉最后一位 | (101101->10110) | x shr 1
在最后加一个0 | (101101->1011010) | x shl 1
在最后加一个1 | (101101->1011011) | x shl 1+1
把最后一位变成1 | (101100->101101) | x or 1
把最后一位变成0 | (101101->101100) | x or 1-1
最后一位取反 | (101101->101100) | x xor 1
把右数第k位变成1 | (101001->101101,k=3) | x or (1 shl (k-1))
把右数第k位变成0 | (101101->101001,k=3) | x and not (1 shl (k-1))
右数第k位取反 | (101001->101101,k=3) | x xor (1 shl (k-1))
取末三位 | (1101101->101) | x and 7
取末k位 | (1101101->1101,k=5) | x and (1 shl k-1)
取右数第k位 | (1101101->1,k=4) | x shr (k-1) and 1
把末k位变成1 | (101001->101111,k=4) | x or (1 shl k-1)
末k位取反 | (101001->100110,k=4) | x xor (1 shl k-1)
把右边连续的1变成0 | (100101111->100100000) | x and (x+1)
把右起第一个0变成1 | (100101111->100111111) | x or (x+1)
把右边连续的0变成1 | (11011000->11011111) | x or (x-1)
取右边连续的1 | (100101111->1111) | (x xor (x+1)) shr 1
去掉右起第一个1的左边 | (100101000->1000) | x and (x xor (x-1))
最后这一个在树状数组中会用到。
Pascal和C中的16进制表示
Pascal中需要在16进制数前加$符号表示,C中需要在前面加0x来表示。这个以后我们会经常用到。
整数类型的储存
我们前面所说的位运算都没有涉及负数,都假设这些运算是在unsigned/word类型(只能表示正数的整型)上进行操作。但计算机如何处理有正负符号的整数类型呢?下面两个程序都是考察16位整数的储存方式(只是语言不同)。
var
a,b:integer;
begin
a:=00;
b:=01;
write(a,' ',b,' ');
a:=$FFFE;
b:=$FFFF;
write(a,' ',b,' ');
a:=FFF;
b:=00;
writeln(a,' ',b);
end.
#include <stdio.h>
int main()
{
short int a, b;
a = 0x0000;
b = 0x0001;
printf( "%d %d ", a, b );
a = 0xFFFE;
b = 0xFFFF;
printf( "%d %d ", a, b );
a = 0x7FFF;
b = 0x8000;
printf( "%d %d\n", a, b );
return 0;
}
两个程序的输出均为0 1 -2 -1 32767 -32768。其中前两个数是内存值最小的时候,中间两个数则是内存值最大的时候,最后输出的两个数是正数与负数的分界处。由此你可以清楚地看到计算机是如何储存一个整数的:计算机用00到FFF依次表示0到32767的数,剩下的00到$FFFF依次表示-32768到-1的数。32位有符号整数的储存方式也是类似的。稍加注意你会发现,二进制的第一位是用来表示正负号的,0表示正,1表示负。这里有一个问题:0本来既不是正数,也不是负数,但它占用了00的位置,因此有符号的整数类型范围中正数个数比负数少一个。对一个有符号的数进行not运算后,最高位的变化将导致正负颠倒,并且数的绝对值会差1。也就是说,not a实际上等于-a-1。这种整数储存方式叫做“补码”。
位运算简介及实用技巧(二):进阶篇(1)
===== 真正强的东西来了! =====
二进制中的1有奇数个还是偶数个
我们可以用下面的代码来计算一个32位整数的二进制中1的个数的奇偶性,当输入数据的二进制表示里有偶数个数字1时程序输出0,有奇数个则输出1。例如,1314520的二进制101000000111011011000中有9个1,则x=1314520时程序输出1。
var
i,x,c:longint;
begin
readln(x);
c:=0;
for i:=1 to 32 do
begin
c:=c + x and 1;
x:=x shr 1;
end;
writeln( c and 1 );
end.
但这样的效率并不高,位运算的神奇之处还没有体现出来。
同样是判断二进制中1的个数的奇偶性,下面这段代码就强了。你能看出这个代码的原理吗?
var
x:longint;
begin
readln(x);
x:=x xor (x shr 1);
x:=x xor (x shr 2);
x:=x xor (x shr 4);
x:=x xor (x shr 8);
x:=x xor (x shr 16);
writeln(x and 1);
end.
为了说明上面这段代码的原理,我们还是拿1314520出来说事。1314520的二进制为101000000111011011000,第一次异或操作的结果如下:

XOR
---------------------------------------

得到的结果是一个新的二进制数,其中右起第i位上的数表示原数中第i和i+1位上有奇数个1还是偶数个1。比如,最右边那个0表示原数末两位有偶数个1,右起第3位上的1就表示原数的这个位置和前一个位置中有奇数个1。对这个数进行第二次异或的结果如下:

XOR
---------------------------------------

结果里的每个1表示原数的该位置及其前面三个位置中共有奇数个1,每个0就表示原数对应的四个位置上共偶数个1。一直做到第五次异或结束后,得到的二进制数的最末位就表示整个32位数里有多少个1,这就是我们最终想要的答案。
计算二进制中的1的个数
同样假设x是一个32位整数。经过下面五次赋值后,x的值就是原数的二进制表示中数字1的个数。比如,初始时x为1314520(网友抓狂:能不能换一个数啊),那么最后x就变成了9,它表示1314520的二进制中有9个1。
x := (x and 555555) + ((x shr 1) and 555555);
x := (x and 333333) + ((x shr 2) and 333333);
x := (x and F0F0F0F) + ((x shr 4) and F0F0F0F);
x := (x and FF00FF) + ((x shr 8) and FF00FF);
x := (x and 00FFFF) + ((x shr 16) and 00FFFF);
为了便于解说,我们下面仅说明这个程序是如何对一个8位整数进行处理的。我们拿数字211(我们班某MM的生日)来开刀。211的二进制为11010011。
+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <---原数
+---+---+---+---+---+---+---+---+
| 1 0 | 0 1 | 0 0 | 1 0 | <---第一次运算后
+-------+-------+-------+-------+
| 0 0 1 1 | 0 0 1 0 | <---第二次运算后
+---------------+---------------+
| 0 0 0 0 0 1 0 1 | <---第三次运算后,得数为5
+-------------------------------+
整个程序是一个分治的思想。第一次我们把每相邻的两位加起来,得到每两位里1的个数,比如前两位10就表示原数的前两位有2个1。第二次我们继续两两相加,10+01=11,00+10=10,得到的结果是00110010,它表示原数前4位有3个1,末4位有2个1。最后一次我们把0011和0010加起来,得到的就是整个二进制中1的个数。程序中巧妙地使用取位和右移,比如第二行中333333的二进制为00110011001100....,用它和x做and运算就相当于以2为单位间隔取数。shr的作用就是让加法运算的相同数位对齐。
二分查找32位整数的前导0个数
这里用的C语言,我直接Copy的Hacker's Delight上的代码。这段代码写成C要好看些,写成Pascal的话会出现很多begin和end,搞得代码很难看。程序思想是二分查找,应该很简单,我就不细说了。
int nlz(unsigned x)
{
int n;
if (x == 0) return(32);
n = 1;
if ((x >> 16) == 0) {n = n +16; x = x <<16;}
if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
n = n - (x >> 31);
return n;
}
只用位运算来取绝对值
这是一个非常有趣的问题。大家先自己想想吧,Ctrl+A显示答案。
答案:假设x为32位整数,则x xor (not (x shr 31) + 1) + x shr 31的结果是x的绝对值
x shr 31是二进制的最高位,它用来表示x的符号。如果它为0(x为正),则not (x shr 31) + 1等于000000,异或任何数结果都不变;如果最高位为1(x为负),则not (x shr 31) + 1等于$FFFFFFFF,x异或它相当于所有数位取反,异或完后再加一。
高低位交换
这个题实际上是我出的,做为学校内部NOIp模拟赛的第一题。题目是这样:
给出一个小于2^32的正整数。这个数可以用一个32位的二进制数表示(不足32位用0补足)。我们称这个二进制数的前16位为“高位”,后16位为“低位”。将它的高低位交换,我们可以得到一个新的数。试问这个新的数是多少(用十进制表示)。
例如,数1314520用二进制表示为0000 0000 0001 0100 0000 1110 1101 1000(添加了11个前导0补足为32位),其中前16位为高位,即0000 0000 0001 0100;后16位为低位,即0000 1110 1101 1000。将它的高低位进行交换,我们得到了一个新的二进制数0000 1110 1101 1000 0000 0000 0001 0100。它即是十进制的249036820。
当时几乎没有人想到用一句位操作来代替冗长的程序。使用位运算的话两句话就完了。
var
n:dword;
begin
readln( n );
writeln( (n shr 16) or (n shl 16) );
end.
而事实上,Pascal有一个系统函数swap直接就可以用。
二进制逆序
下面的程序读入一个32位整数并输出它的二进制倒序后所表示的数。
输入: 1314520 (二进制为)
输出: 460335104 (二进制为)
var
x:dword;
begin
readln(x);
x := (x and 555555) shl 1 or (x and $AAAAAAAA) shr 1;
x := (x and 333333) shl 2 or (x and $CCCCCCCC) shr 2;
x := (x and F0F0F0F) shl 4 or (x and $F0F0F0F0) shr 4;
x := (x and FF00FF) shl 8 or (x and $FF00FF00) shr 8;
x := (x and 00FFFF) shl 16 or (x and $FFFF0000) shr 16;
writeln(x);
end.
它的原理和刚才求二进制中1的个数那个例题是大致相同的。程序首先交换每相邻两位上的数,以后把互相交换过的数看成一个整体,继续进行以2位为单位、以4位为单位的左右对换操作。我们再次用8位整数211来演示程序执行过程:
+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <---原数
+---+---+---+---+---+---+---+---+
| 1 1 | 1 0 | 0 0 | 1 1 | <---第一次运算后
+-------+-------+-------+-------+
| 1 0 1 1 | 1 1 0 0 | <---第二次运算后
+---------------+---------------+
| 1 1 0 0 1 0 1 1 | <---第三次运算后
+-------------------------------+
位运算简介及实用技巧(三):进阶篇(2)
今天我们来看两个稍微复杂一点的例子。
n皇后问题位运算版
n皇后问题是啥我就不说了吧,学编程的肯定都见过。下面的十多行代码是n皇后问题的一个高效位运算程序,看到过的人都夸它牛。初始时,upperlim:=(1 shl n)-1。主程序调用test(0,0,0)后sum的值就是n皇后总的解数。拿这个去交USACO,0.3s,暴爽。
procere test(row,ld,rd:longint);
var
pos,p:longint;
begin
if row<>upperlim then
begin
pos:=upperlim and not (row or ld or rd);
while pos<>0 do
begin
p:=pos and -pos;
pos:=pos-p;
test(row+p,(ld+p)shl 1,(rd+p)shr 1);
end;
end
else inc(sum);
end;
乍一看似乎完全摸不着头脑,实际上整个程序是非常容易理解的。这里还是建议大家自己单步运行一探究竟,实在没研究出来再看下面的解说。
和普通算法一样,这是一个递归过程,程序一行一行地寻找可以放皇后的地方。过程带三个参数,row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。我们以6x6的棋盘为例,看看程序是怎么工作的。假设现在已经递归到第四层,前三层放的子已经标在左图上了。红色、蓝色和绿色的线分别表示三个方向上有冲突的位置,位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)。前面说过-a相当于not a + 1,这里的代码第6行就相当于pos and (not pos + 1),其结果是取出最右边的那个1。这样,p就表示该行的某个可以放子的位置,把它从pos中移除并递归调用test过程。注意递归调用时三个参数的变化,每个参数都加上了一个禁位,但两个对角线方向的禁位对下一行的影响需要平移一位。最后,如果递归到某个时候发现row=111111了,说明六个皇后全放进去了,此时程序从第1行跳到第11行,找到的解的个数加一。
~~~~====~~~~===== 华丽的分割线 =====~~~~====~~~~
Gray码
假如我有4个潜在的GF,我需要决定最终到底和谁在一起。一个简单的办法就是,依次和每个MM交往一段时间,最后选择给我带来的“满意度”最大的MM。但看了dd牛的理论后,事情开始变得复杂了:我可以选择和多个MM在一起。这样,需要考核的状态变成了2^4=16种(当然包括0000这一状态,因为我有可能是玻璃)。现在的问题就是,我应该用什么顺序来遍历这16种状态呢?
传统的做法是,用二进制数的顺序来遍历所有可能的组合。也就是说,我需要以0000->0001->0010->0011->0100->...->1111这样的顺序对每种状态进行测试。这个顺序很不科学,很多时候状态的转移都很耗时。比如从0111到1000时我需要暂时甩掉当前所有的3个MM,然后去把第4个MM。同时改变所有MM与我的关系是一件何等巨大的工程啊。因此,我希望知道,是否有一种方法可以使得,从没有MM这一状态出发,每次只改变我和一个MM的关系(追或者甩),15次操作后恰好遍历完所有可能的组合(最终状态不一定是1111)。大家自己先试一试看行不行。
解决这个问题的方法很巧妙。我们来说明,假如我们已经知道了n=2时的合法遍历顺序,我们如何得到n=3的遍历顺序。显然,n=2的遍历顺序如下:
00
01
11
10
你可能已经想到了如何把上面的遍历顺序扩展到n=3的情况。n=3时一共有8种状态,其中前面4个把n=2的遍历顺序照搬下来,然后把它们对称翻折下去并在最前面加上1作为后面4个状态:
000
001
011
010 ↑
--------
110 ↓
111
101
100

8. C语言中的;号怎么使用

一条语句结束一般要用分号
如果你想几条语句用一个分号也是可以的
例如几条语句写在一行上用逗号隔开
或者用逻辑符号隔开
下面是一个打印99表的例子
[Make@Make ~]$ cat hehe.c
#include <stdio.h>
int r;main(_){(++r>_?puts(""),r=0,_++^9:printf("%d*%d=%-4d",r,_,_*r))&&main(_);}
[Make@Make ~]$ gcc hehe.c
[Make@Make ~]$ ./a.out
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
[Make@Make ~]$

9. c语言中,普通字符常量单撇号下,是否可以用一个汉字,例如:‘好’,这样是否合法。

不合法,汉字是长字节的,而''是短字节。
汉字还有好几种编码方式,比如Unicode,“好”的字符就是由0xBA,0XC3两个组合的。具体使用,可以用楼上的方式来用,但它的长度是短字符的两倍。
上面的意思是告诉你,'好'是代表着一个整数,int类型的,不能转换为char类型的,默认把单个汉字当着整数。

10. 关于c语言中变量有外部链接内部链接和无 链接怎么理解

这部分内容是在程序的编译和link层面谈的。
一个大一些的工程往往不是只有一个程序文件,经常由好多C程序文件构成,有的时候里面个别程序可能还用的其他语言,编码完成后常常分别编译,编译完成再link到一起。某个C程序需要用到其他程序中定义过的变量,一般都加extern前缀,编译时编译器会预留访问链接的空位,等到link阶段再在整个工程的其他C编译结果中去对号,把访问链接填上。这就是外部链接。如果你程序全写在一个文件里,那永远都不会有外部链接。
内部链接常指一个程序文件中全局变量,可以被程序文件内各个子程序访问,这在编译过程中处理,和link阶段不发生关系。如果变量前加了static,那么它永远不会被外部程序访问,它不会被编译程序写入目标代码的链接区。
无链接,就是在一个单体程序里,比如一个子程序,定义一个变量只给这个程序段用,那就是无链接。编译器和link都不需要对这样的变量做跨程序段的地址链接,这样的变量都是直接分配寄存器或者近堆中的直接地址(每个子程序都有自己的基本存储空间,被调用时得到分配,返回时被释放,我习惯叫它近堆,标准叫啥早不记得了)。
变量是这样,程序代码段也大体差不多。每次在程序文件中调用一个文件内部的子程序,就产生一个内部链接;如果调用外部文件中的子程序,就产生一个外部链接。只有没有任何子程序,所有代码都写在一个文件里的程序,才是无链接程序。
链接是个编译和link层面的概念,所以不仅限于变量层面讨论。
对了,再延伸,可以把一些子程序文件归类,程序执行某部分任务才访问,其他时间不访问时,可以生成DLL。在程序执行那部分功能时,通过操作系统和DLL建立动态链接,当然这是外部链接,这也是程序设计中常用到的。这可以避免生成一个巨大的EXE,运行时吃掉过多的系统资源,还可以实现这个DLL中包含的子程序在操作系统级和其他程序共用。