本篇笔记参考了《C语言程序设计-现代方法》和浙大翁恺的C语言视频。
返回:输出的字符数
%[flags][width][.prec][hil]type
flag | width或.prec | hil |
---|---|---|
-:左对齐,和width同时使用 | number:最小字符数 | hh:单个字节 |
+:在前面放+ | *:下一参数是字符数 | h:short |
(space):正数留空 | .:number小数点后的位数 | l:long |
0:0填充 | .*:下一参数是小数点后的位数 | ll:long long |
L:long double |
type | 含义 | type | 含义 |
---|---|---|---|
i或d | int | g或G | float |
u | unsigned int | a或A | 十六进制浮点 |
o | 八进制 | c | char |
x | 十六进制 | s | 字符串 |
X(大写) | 字母大写的十六进制 | p | 指针 |
f或F | float, 6 | n | 读入/写出的个数 |
e或E | 指数 |
printf("%9d\n", 123); //123
printf("%.9d\n", 123); //000000123
printf("%+9d\n", 123); //+123
printf("%-9d\n", 123); //123
printf("%+-9d\n", 123); //+123
printf("%9.2f\n", 123); //0.00,前后格式不对
printf("%9.2f\n", 123.0); //123.00
printf("%-*.2f\n", 9, 123.0); //9代入*的位置
printf("%-9.*f\n", len, 123.0); //len这个变量代入*的位置
printf("%hhd\n", (char)12345); //12345转化为十六进制为0x3039,单字节(低位)为0x39,即十进制的57
#include
#include /*Sleep函数要用到*/
int main()
{
int i;
for (i = 0; i <= 100; i++)
{
printf("Percent completed: %3d%%\r", i); /*\r表示光标换到本行行首。意味着可以覆盖*/
Sleep(1000); /* 里面填写毫秒数。注意Sleep大写*/
}
return 0;
}
返回:读入的项目数
scanf("%d%d%f%f", &i, &j, &x, &y);
printf("i = %d, j = %d, x = %f, y = %f", i, j, x, y);
%[flag][type]
flag | 含义 | flag | 含义 |
---|---|---|---|
* | 跳过 | l | long, double |
数字 | 最大字符数 | ll | long long |
hh | char | L | long double |
h | short |
type | 含义 | type | 含义 |
---|---|---|---|
d | int | a, e, f, g | float |
i | 整数,可能为十六进制或八进制(输入时前面加0x或0) | c | char |
u | unsigned int | s | 字符串(单词) |
o | 八进制 | […] | 所允许的字符 |
x | 十六进制 | p | 指针 |
int number;
scanf("%*d%d", &number); //假设输入123 456
printf("%d\n", number); //只输出456
int num;
int i1 = scanf("%i", &num); //输入1234
int i2 = printf("%d\n", num); //这里输出1234
printf("%d:%d", i1, i2); //输出1:5(i2包含换行符)
使用"\”反斜杠进行换行时,必须确保反斜杠后面不能有东西,连注释都不能有
运算符=是右结合的,所以i = j = k = 0 等价于i = (j = (k = 0))。同时,还会带来类型转化的问题。
int i;
float f;
f = i = 33.f; /*f = 33.0, i = 33 */
说明 | 举例 | |
---|---|---|
左值 | 存储在计算机内存中的对象 | 变量。左值表达式表示了一块内存区域。正因此,可以用& 取地址的表达式 |
右值 |
以下表达式均是错误的
12 = i; //错误
i + j = 0; //错误
C99开始,才对布尔值进行了定义。
_Bool flag; /*_Bool本质是无符号整数,不过其值只能存0或1*/
#include
bool flag; /*也可以用bool来定义,不过要包含stdbool.h头文件*/
数字越小,优先级越高。
算术运算符 > 关系运算符 > && > || > 赋值运算符
优先级 | 结合方式 | 备注 | ||
---|---|---|---|---|
1 | ||||
2 | ||||
3 | ||||
4 | ||||
6 | ||||
7 | 关系运算符 | <, <=,>,>= | 左结合 | i < j < k等价于(i < j) < k,即先判断括号里的真假,再和k比较,再输出真假 |
8 | 判等运算符 | ==,!= | 左结合 | i < j == j < k等价于(i < j) == (j < k) |
9 | ||||
10 | ||||
11 | ||||
12 | 逻辑与AND | && | 左结合 | |
13 | 逻辑或OR | || | 左结合 | |
14 | ||||
15 | ||||
16 | ||||
17 |
格式:
if (表达式)
单语句;
===================
if (表达式)
{
单语句;
单语句;
}
=====================
if (表达式)
语句;
else
语句;
=====================
if (表达式)
语句;
else if (表达式)
语句;
else if (表达式)
语句;
else
语句;
执行语句时, 先计算圆括号内表达式的值。如果表达式的值非零(C语言把非零值解释为真值), 那么接着执行圆括号后边的语句。
默认if后面只执行一条语句,多语句记得用大括号!
else与最近的且未匹配的if配对,和缩进无关。
条件表达式
if 表达式1?表达式2:表达式3
if (i > j)
return i;
else
return j;
等价于return i > j ? i : j;
============================
格式
switch (控制表达式){
case 常量:
语句;
break;
case 常量:
语句;
break;
case 常量:
...
default:
语句;
break;
}
switch-case语句可以起到多个else-if语句的功能。不同之处在于
常量可以是常数,也可以是常数计算的表达式
case表示进入的路标,break表示推出switch-case的路标。如果一个case里面没有break,那么程序会继续往下执行直到遇到break。有时故意这样设计以达到多个case公用同样的表达式。见下面的代码。给不同日期加上不同的英文后缀
switch (day)
{
case 1: case 21: case 31:
printf("st");break;
case 2: case 22:
printf("nd");break;
case 3: case 23:
printf("rd");break;
default: printf("th");break;
}
switch语句不要求一定有default分支。如果default不存在,而且控制表达式的值和任何一个分支标号都不匹配的话,控制会直接传给switch语句后面的语句。
格式
while (条件)
语句;
while (条件)
{
语句;
语句;
}
类似的if语句,当有多条语句时,采用大括号括起来
printf函数本身可以做到循环变量改变的特点,如下
int main()
{
int i = 10;
while (i > 0)
{
printf("T minutes %d and counting.\n", i--);
}
return 0;
}
while (i)
printf("T minutes %d and counting.\n", i--);
如何做到两列整齐的输出?
做法是
printf("%10d%10d\n", i, i * i);
i++和++i的区别?
i++表示先参与运算,再令i自增。++i表示先自增,再参与运算
int i = 1;
//打印时,按1 2 3 4 .。。打印
do
{
printf("%d\n", i++);
}
//===================================
int i = 1;
//打印时,按2 3 4 5.。。打印
do
{
printf("%d\n", ++i);
}
格式:
for (表达式1;表达式2;表达式3)
语句;
//=============
for (表达式1;表达式2;表达式3)
{
语句;
语句3;
}
等价于
表达式1; //即初始化
while (表达式2)
{
语句;
表达式3;
}
可以省略3个表达式中的部分,但此时省略的部分需要在其他地方标明。
i = 10;
for (;i > 0;--i)
{
printf("%d\n",i)
}
//============================
for (i = 10;i > 0;)
{
printf("%d\n",i--)
}
for (; ;)
{
读入数据;
if (数据的第一条测试)
continue;
if (数据的第二条测试)
continue;
if (数据的第n条测试)
continue;
处理数据;
}
**在C99中,允许在表达式1进行声明,且可以进行多个声明。但此声明只能在for循环里使用。**看下面
int N = 10;
for (sum = 0, i = 1; i <= N; i++)
{
sum += i;
}
break:用于跳出当前循环。当有多层循环时,只能跳出当前的一层。能用于switch和循环(while、do-whie和for)。
continue:用于跳到当前循环的最末尾,但仍没有跳出循环。只能用于循环(while、do-whie和for)。
n = 0;
sum = 0;
while (n < 10)
{
scanf("%d", &i;)
if (i == 0)
continue;
sum += i;
n++;
/*continue jumps to here*/
}
goto:配合标识符使用。【标号语句为】:标识符:语句。【goto语句】:goto 标识符
for (d = 2; d < n; d++)
if (n % d == 0)
goto done;
done:
if (d < n)
printf("%d is divisible by %d\n", n, d);
else
printf("%d is prime\n", n)
标识符后的语句必须和goto语句在同一个函数中。
有符号数:C语言默认。最左边一维表示符号,0表示正数/零,1表示负数。例如有符号16位的最大值位215-1
无符号数:需要用unsigned声明。无符号16位整型的最大值为216-1
可用sizeof(int)等语句测试该类型的取值范围。在32和64位机器中,int为4 bytes,所以取值范围为-231~231-1
long long int的取值范围是-263~263-1
类型声明的组合:
整数常量:
整数溢出:此时仅仅改变类型不够,还要审视其他涉及到此数据的语句,如printf中的%d
读写整数:
无符号整数 | %u %o %x |
十进制 八进制 十六进制 |
---|---|---|
短整型 | 前面加上h | %hu %ho %hx |
长整型 | 前面加上l | |
长长整型 | 前面加上ll |
float | 单精度 | 32位 |
---|---|---|
double | 双精度 | 64位 |
long double | 扩展双精度 |
double d;
scanf("lf", &d); /* %f表示读取float类型,%lf表示读取double类型。但在显示float和double时,都用%f */
float annwer = 17 / 13; /*结果为1.00,因为右侧是两个整型相除,得到1。然后类型转换为浮点型,为1.00 */
float answer = 17.0 / 13.0; /*结果为1.30769 */
float answer = 17 / (float) 13; /*结果为1.30769*/
float f;
float c = 5 / 9.0 * (f - 32); /*虽然上一行已经将f声明为浮点数,但这里的顺序是,先算括号,得到的是浮点数,然后有乘除,从左到右的顺序算,所以直接写5/9所得结果会是一个整型的0 */
float c = (f - 32) * 5 / 9; /*等价于上一条,这里已经有浮点了,从左往右计算,后面5/9即可 */
常见字符对应数字
字符 | 十进制数 |
---|---|
A | 65 |
a | 97 |
0 | 48 |
空格 | 32 |
字符往往需要用单引号‘ 括起来,如’A’
字符常量其实是int类型,而不是char类型。所以可用对char类型的数据进行计算、比较
char ch;
int i;
i = 'a'; //i is now 97
ch = 65; //ch is now 'A'
ch = ch + 1; //ch is now 'B'
ch++; //CH is now 'C'
if ('a' <= ch && ch >= 'z')
ch = ch - 'a' + 'A'; //把小写字母转化成大写字母
//=======相当于以下函数==============
#inlcude <ctype.h> //使用toupper必须含义此头文件
ch = toupper(ch) //当参数是小写时,返回大写。否则返回本身
有符号字符和无符号字符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVLEk4S9-1653993643083)(D:\Documents\C\images\image-20220220201831067.png)]
使用%c来在scanf和printf中读写字符
当需要跳过空格时,需要写成scanf(“ %c”, ch),注意%c前面有个空格
getchar和putchar表示读和写单个字符。getchar函数返回的是一个int类型。getchar函数比scanf函数更有效率。
/*计算一个句子由多长*/
int main()
{
int length = 0;
char ch;
printf("Input a sentence:");
while (getchar() != '\n')
length++;
printf("Your message is %d character(s) long.", length);
return 0;
}
getchar惯用法
while (getchar() != '\n') /*跳过换行后的内容*/
;
while ((ch = getchar()) == ' ') /*跳过空格*/
;
赋值转化
int i;
char c;
i = 843.56; //now i is 843
i = -843.56; //now i is -843
i = i + c; //c会被转化为
算术转化
强制转化
使用**(类型名)表达式**来强制转化。如
long i;
int j = 1000;
i = j * j; //有溢出。因为j是int型,j*j还是int型,1000000大于int的范围,i被转化为int通用出问题。
i = (long)j * j; /*强制转化运算符优先级最高,把左边的j转化为long int,右边的j,赋值左边的i都变成long in */
采用类型定义会使得编译器将新类型加入类型列表中
typedef int Bool;
Bool flag //same as int flag
#define BOOL int
类型定义的优点:
更易理解。如
typedef float Dollars;
Dollars case_in, case_out;
更易修改和移植。如要把float修改成double型,只需要把前面修改成type double Dollars;在代码移植过程中,前后机器的类型的取值范围不同,使用类型定义可方便修改数据类型。
可移植性
不同位机器,如32位机器和16位机器对于int的取值范围是不同的。为了使移植时不出错,那么可使用typedef类型定义来快速修改。这些因实现的不同而改变 (实现定义的, implementation-defined) 的类型名经常以_t为后缀。如下
typedef unsigned long int size_t;
typedef int wchar_t;
数组声明
#define N 10 /* 为了避免以后更改数组的长度,可以用宏来定义 */
int a[N]; /*前面int表示的是元素的类型,一个数组里所以元素的类型相同 */
a[i]的表达式是左值,可以当成变量一样使用。如下
scanf("%d\n", &a[i]); //注意&符号
a[i + j * 10] = 0;
数组初始化
int a[5] = {0, 1, 2, 3, 4};
int a[10] = {0, 1, 2, 3, 4, 5}; //a[6]~a[10]均为0
int a[10] = {0}; //a[0] = 0, 其他未填默认为0。利用此特性可快速初始化所有元素为0.
int a[] = {0, 1, 2, 3, 4}; //也可以不指定数组的长度
int a[100] = {5, 1, 9, [6] = 9, 56, [12] = 2, [5] = 99} /*前面三个数是5,1,9,a[6]和a[7]分别为9和56,a[5为99],其余为0.这样的初始化方式,不需要顺序。 */
int a[] = {[6] = 9, 56, [12] = 2} /*这样写时,数组长度为13 */
[0]=5
使得a[0]上的4被替换成5,此时下标继续往下走,所以a[1]=7。最终数组a[4] = {5, 7, 1, 8}
.int a[] = {4, 9, 1, 8, [0]=5, 7};
memcpy(a, b, sizeof(a));
计算数组长度
sizeof(a); //计算数组a所占字节数。返回一个无符号类型size_t
sizeof(a[0]); //计算元素所占字节数。返回一个无符号类型size_t
sizeof(a) / sizeof(a[0]); //得出长度。sizeof返回一个无符号类型size_t
(int) (sizeof(a) / sizeof(a[0])); //强制转化为有符号整数,以免报错
#define SIZE (int) (sizeof(a) / sizeof(a[0])) //宏定义,以免下面的代码太难写
常量数组:数组前使用const可另数组变成常量,不允许修改
随机函数srand和rand
void srand(unsigned int seed);
/*
*发牌程序,关键点在于发了一个牌之后要判定这个牌有没有发过。所以引入了true和false===
*/
#include
#include
#include
#include
#define NUM_SUITS 4
#define NUM_RANKS 13
int main()
{
bool in_hand[NUM_SUITS][NUM_RANKS] = {false};
int num_card, rank, suit;
const char rank_code[] = {'2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'};
const char suit_code[] = {'d', 'c', 'h', 's'}; /*"diamond", "club", "heart", "spade"*/
srand((unsigned) time(NULL)); /*使得每次程序运行的随机数都不一样*/
printf("Enter number(s) of cards in hand: ");
scanf("%d", &num_card);
printf("Your hand: ");
while (num_card > 0)
{
rank = rand() % NUM_RANKS;
suit = rand() % NUM_SUITS;
if (!in_hand[rank][suit])
{
in_hand[rank][suit] = true;
num_card--;
printf("%c_%c ",rank_code[rank], suit_code[suit]);
}
}
return 0;
}
scanf("%d", &n);
int a[n]; /*由变量n决定*/
int b[3 * n +5]; /*也可以用表达式决定*/
逗号运算符
格式
表达式1,表达式2
计算过程:分别计算处表达式1和2的值,并且把表达式2作为整个表达式的值。
二维数组的声明:
int a[m][n]; //该声明产生m行n列的二维数组
访问第m行n列的元素时,写成a[m][n]的形式,不能写成a[m, n](不要和数学上的混淆),否则方括号里面的会被当成逗号运算符,a[m, n] == a[n]。
二维数组在内存中是按行排列的,即先排完第1行的内容,再排第二行的内容。
二维数组的初始化
int m[5][9] = {{1,2,3,4,5,6,7,8,9},{2,3,4,5,6,7,8,9,10}} /*即大括号里面有小括号,小括号括住的是一行的内容。其他初始方法和一维数组类似 */
格式
double是函数返回的类型。不带返回值时,前面为void。当返回类型很长,如unsigned long int时,返回来行可单独成一行。如
unsigned long int
average(double a, double b)
函数名为average
a和b为形参(parameter),每一个形参都需要单独说明类型。没有时写void。
{ }括起来的部分为函数体
函数只能返回一个值
double average(double a, double b)
{
return (a + b) / 2;
}
int line;
printf("How many lines?\n");
scanf("%d", &line);
if (line < 1)
{
printf("Sorry, that makes no sense.\n");
return 1; /*若用户输入一个非正整数,提示报错*/
}
int product(int v[3], int w[3])
中,虽然程序编写者想提示应该传长度为3的数组进函数,实际可以传任意长度的数组。格式
返回类型 函数名(形式参数);
double average(double a, double b);
double average(double, double); //也可行,
C语言要求在调用函数时编译器已经知道函数,所有有两种方法
形式参数(parameter) | 出现在函数定义中 |
---|---|
实际参数(argument) | 出现在函数调用的表达式 |
void square(int x);
int main()
{
double x = 2.5;
square(x); /*输出4,而不是6.25*/
return 0;
}
void square(int x)
{
printf("square(x) = %d\n", x * x);
}
数组经常被当作实际参数,当形式参数是一维数组时,可以(实际经常这么做)不说明数组的长度。如果需要知道数组的长度,必须传入相关的参数。
int f(int a[]) /* no length specified */
{
...
}
/* ========================================= */
int f(int[], int) /*更简便的写法,省略形式参数的名 */
{
...
}
/* ========================================= */
int sum(int a[][LEN], int n); /*当形参为二维数组时,列的数量必须要表明*/
int sum_array(int a[], int n); /* 该函数声明了一个可以求数组所有函数的和的函数,第一个形参为数组,第二个形参为数组的长度 */
total = sum_array(a, n); /* 实际参数只需要写名,不能写成a[] */
void store_zero(int a[], int n)
{
int i;
for (i = 0; i < n; i++)
a[i] = 0;
}
store_zero(b, 100); /*数组b被改变*/
int sum_array(int n, int a[n])
{
...
}
int sum_array(int n, int m, int a[n][m]) /*二维变长数组*/
{
...
}
/* 声明时可以按以下声明 */
int sum_array(int n, int a[n]);
int sum_array(int n, int a[*]); //用*代替长度 */
int sum_array(int, int [*]);
int sum_array(int n, int a[]) //函数中空
int sum_array(int, int []);
int sum_array(int a[static 3], int n) //表面数组a的长度至少是3
{
...
}
如果数组是多维的,static仅可用于第一维(行数)
数组中的复合字面量
格式
(给定类型){元素的值}
/* b作为一个变量声明,需要在调用前进行初始化。如果b不作它用,比较浪费。*/
int b[] = {3, 0, 3, 4, 1};
total = sum_array(b, 5);
total = sum_array((int []){3, 0, 3, 4, 1}, 5); /*这就是复合字面量*/
total = sum_array((int []) {2*i, i+j, j*k}, 3); /*复合字面量可以包含表达式*/
格式
return 表达式;
return;
main函数
exit函数
exit函数也可用于终止程序。
#include //EXTIT_SUCCESS和EXIT_FAILURE定义在此宏中,使用时要加上
exit(0); /*0表示正常终止*/
exit(EXTIT_SUCCESS); /*等效于exit(0)*/
exit(EXIT_FAILURE); /*程序异常终止,等效于exit(0)*/
函数 | 说明 |
---|---|
return | 仅当main函数调用时才会导致程序终止 |
exit | 任何函数调用都会导致程序终止 |
函数可以调用它本身
/* 利用n! == n * (n-1)!实现阶乘 */
int fact(int n)
{
if (n <= 1) /*一定要设置边界*/
return 1;
else
return n * fact(n-1);
}
int fact(int n)
{
return n <= 1 ? 1 : n * fact(n-1); /*可精简成这样*/
}
void quicksort(int a[], int left, int right) /*left和right表示区间端点*/
{
if (left < right) /*以免出bug*/
{
int i = left, j = right, ref = a[left]; /*i和j是游离的下标,是用来扫描元素和ref比较的*/
while (i < j)
{
while (i < j && a[j] >= ref)
j--; /*从右往左,找到a[j] < ref的下标j,即需要调换区间的那个元素的下标*/
if (i < j)
a[i++] = a[j]; /*将a[j]调换到a[i],然后i++使i右移*/
while (i < j && a[i] <= ref)
i++;
if (i < j)
a[j--] = a[i];
}
a[i] = ref; /*此时i == j,所以把ref放到a[i]这个位置。第一次分出了两个小区间。下面是利用递归对小区间排序*/
quicksort(a, left, i - 1);
quicksort(a, i + 1, right);
}
}
定义:函数体内声明的变量称为该函数的局部变量。
自动存储期限(storage duration):局部变量的存储单元在函数被调用时“自动”分配,在函数返回时收回分配,。
/*文件a.c*/
char a = 'A'; // global variable
void msg()
{
printf("Hello\n");
}
/*main.c*/
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序运行结果:A Hello
因为未加static所以a.c中的变量和函数能在main.c中被使用
#include
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;
}
上述程序运行结果:因为只初始化一次,fun()不会一直返回10
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
块作用域:所以其他函数可以把同名变量用于别的用途。
静态局部变量:在变量声明中使用static,可以使变量具有静态存储期限而不是自动存储期限。拥有静态存储期限的变量获得永久的存储单元,不会丢失其值。但身为局部变量,有块作用域,静态局部变量仍不能被其他函数可见。
f(); //打印1和3
f(); //打印3和5
f(); //打印5和7
int f()
{
static int all = 1;
printf("%d\n", all);
all += 2;
printf("%d\n", all);
return all;
}
全局变量又称为外部变量。全局变量的特点
静态存储期限:其中的值永久被保留
文件作用域:从变量被声明的点到文件末尾,所有函数都可访问(并修改)它。
初始化:只能用常量来初始化,不能用一个变量来初始化。
注意:如果函数内有和全局变量同名的局部变量,那么全局变量会被隐藏。离开该函数,全局变量就恢复。
#include指令
#define指令
类型定义
外部变量的声明
除main函数之外的函数原型
main函数的定义
其他函数的定义
&:取得变量的的地址,操作的对象必须是变量。下面的表达是错误的
&(a++);
&(--a);
指针变量:专门用于存放地址的变量。如果用其它变量存放(比如int a),可能会因为强制转化把地址丢失掉。
指针变量的值 | 具有实际值的变量的地址 |
---|---|
普通变量的值 | 实际的值 |
注意:
#include
void f(int *p);
int main()
{
int i = 100;
printf("&i = %p\n", &i); /* 注意此处为%p */
f(&i);
return 0;
}
void f(int *p)
{
printf("p = %p\n", p);
printf("*p = %d\n", *p); /* 注意此处为%d */
}
/* 以上程序输出结果 */
&i = 000000000061FE1C
p = 000000000061FE1C
*p = 100
*是一个单目运算,用来访问指针的值所表示的地址上的变量。可以作左值也可以作右值。
int k = *p;
*p = k + 1;
指针应用场景一:交换两个值
#include
void swap(int *pa, int *pb); /*参数是变量*/
int main()
{
int a = 5, b = 6;
swap(&a, &b); /*用地址来达到交换的目的*/
printf("a = %d, b = %d", a, b);
return 0;
}
void swap(int *pa, int *pb)
{
int t = *pa;
*pa = *pb;
*pb = t;
}
指针应用场景二:返回多个值
#include
void minmax(int a[], int len, int *max, int *min);
int main()
{
int a[] = {5, 4, 0, -9, 100, 66, 7};
int min, max;
minmax(a, sizeof(a)/sizeof(a[0]), &min, &max);
printf("min = %d, max = %d", min, max);
return 0;
}
void minmax(int a[], int len, int *max, int *min)
{
int i;
*min = *max = a[0];
for (i = 0; i < len; i++)
{
if (a[i] < *min)
*min = a[i];
if (a[i] > *max)
*max = a[i];
}
}
指针应用场景二b:函数返回运算的结果,结果通过指针返回
/* 如果除法成功则返回1,否则返回0 */
#include
int divide(int a, int b, int *result);
int main()
{
int a = 5;
int b = 3;
int c;
if (divide(a, b, &c)) //1为真,则能进行除法
printf("%d/%d=%d",a,b,c);
return 0;
}
int divide(int a, int b, int *result)
{
int ret = 1;
if (b == 0) ret = 0;
else *result = a/b;
return ret;
}
注意:定义了指针,在没有指向任何变量前,不要对齐进行赋值。否则可能会把内存中不知名的区域数据进行了更改。
/* 返回最大值的地址 */
int *max(int *a, int *b) /* 函数名前面加*表示这是一个返回的指针的指针型函数*/
{
if (*a > *b)
return a;
else
return b;
}
注:不要返回执行自动局部变量的指针,如
int *f(void)
{
int i;
...
return &i
}
在函数参数表中,数组即指针。以下四个表达式在参数表中是等效的
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
int a[10], *p = a;
//p[i]这样的写法没有问题,因为指针可以用作数组名
由于在函数参数中的数组是以指针存在的,所有在函数中用sizeof(a)==sizeof(int *),即不能用sizeof(a)/sizeof(a[0])来计算数组长度。
int a[10];
int *p = a; /*无需用&取地址*/
a == &a[0]; /* 数组的单元表达的是变量,需要用&取地址 */
p[0] == a[0]; /* []可以对数组做,也可以对指针做 */
*a = 25; /*运算符可以对指针做,也可以对数组做 */
数组变量是const的指针,所有不能被赋值。如下
int b[]; /*等价于int * const b*/
b = a; /*错误,因为b为const,不能被赋值 */
指针是const:表示一旦得到了某个变量的地址,不能再指向其他变量
int * const q = &i; //q是const
*q = 26; //OK
q++; //ERROR
所指是const:不能通过这个指针修改那个变量。变量可以变,指针可以变,但不能修改.
const int *p = &i;
*p = 26; //ERROR
i = 26; //OK
p = &j; //OK
int i;
const int* p1 = &i; /*const修饰的是int变量,即所指不能修改,但指针p1可以修改*/
int const* p2 = &i; /*同上*/
int *const p3 = &i; /*const修饰的是常量指针p3,指针不可修改,所指可以修改*/
例子1:
int a = 10;
int b = 20;
const int *c = &a; //const修饰的是int,也即是*c的值不可变,但c指针可变
//int const *d = &a; //同上句代码作用等同
//*c = 20; //取消注释此句会报错,因为*c的内容不可变
c = &b; //可行的
例子2:
int a = 10;
int b = 20;
int *const c = &a; //const修饰的是指针c,所以c是常量指针,但存储的地址所指向的内容可变
//c = &b; //取消注释此句会报错,因为c是常量指针
*c = 30;
例子3:
void func(const int *p)
{
int j;
*p = 0; /*非法的*/
p = &j; /*合法的*/
}
void func1(const int * const p) /*所指和指针均需用const保护*/
{
int j;
*p = 0; /*非法的*/
p = &j; /*非法的*/
}
int sum(const int a[], int length)
C语言只支持3种指针运算
指针+1的结果取决于指针的类型。
sizeof(char) = 1;
sizeof(int) = 4;
char ac[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
char *p = ac;
printf("p = %p\n", p);
printf("p+1 = %p\n", p+1);
int ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *q = ai;
printf("q = %p\n", q);
printf("q+1 = %p\n", q+1);
/* 输出结果 */
p = 000000000061FE06
p+1 = 000000000061FE07
q = 000000000061FDD0
q+1 = 000000000061FDD4
/* 承接上述程序 */
*(p+1) ==>ac[1];
*(p+n) ==>ac[n];
*(q+n) ==>ai[n];
*q + 1; //该运算往往没有实际意义,*q+1=000000000061FDD1,没有意义
char ac[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1};
char *p = ac;
for (p = ac; *p != -1; p++) /* 中间表达式也可写成p<&ac[N],N为数组长度。虽然不存在a[N],但对它取地址是合法的。 中间也可写成p< ac +N */
printf("%d\n", *p);
for (p = ac; *p != -1; p) //上面的替换写法
printf("%d\n", *p++);
while (*p != -1) //上面的替换写法
printf("%d\n", *p++);
NULL:零地址
无论指向什么类型,所有的指针大小都是一样的,因为都是地址;但指向不同类型的指针不能相互赋值,例如上面的例子中,另char的p指针和int的q指针相等,那么另*q = 0的时候,char的数组的连续4个元素都要变成0
指针的类型转换:
int a[N], *P;
for (p = a; p < a + N; p++)
scanf("%d", p) /* p本身是地址了,不必用&p */
malloc:分配内存空间(byte为单位),与free搭配使用
#include
void* malloc(size_t size);
int *x;
x = malloc(sizeof(int)); /*申请内存*/
*x = 42; /*往内存写入东西,注意前面的星号*/
free():把申请来的空间还给“系统”,只能还申请得来空间的首地址。(如p是申请来的,p++后,free§是不允许的。
原则上可把二维数组当成一维数组来处理。如用for循环把所有元素置0,不需使用两层for,使用一层也可以。
对于二维数组而言,p[i]表示第i行的首地址,p[i]+j表示第i行地点j列的元素。
int a[row][col], *p; //假设有row行col列的二维数组
for (p = &a[0][0]; p <= &a[row-1][col-1]; p++) /* 中间不能用p < &a[row][col] */
*p = 0;
处理多维数组的行
p = &a[i][0]; //p为指针
p = a[i]; //上述程序的简写
/*推导:因为对任意数组而言a[i]=*(a+i),故&a[i][0]=&(*(a[i]+0))=&*a[i]=a[i]。注意*和&是一对互为相反的操作 */
处理多维数组的列
/* 把row行col列的数组的第i列*/
int a[row][col], (*p)[col], i; /* 把p声明成长度为col的整型数组的指针。*p是需要括号的,如果没有括号,编译器会把p认作指针数组,而不是指向数组的指针。p++表示移到下一行(而不是下一个元素)*/
for (p = &a[0]; p < &a[row]; p++)
(*p)[i] = 0; /* *p表示a的一整行,(*p)[i]表示选中该行的第i列元素。括号不可少 */
在初始化二维数组时,需要把二维数组的列说明
char a[][10] = {"hello", "world"};
二维数组指针举例:
#include
int main()
{
int a[3][4], *p;
int i, j;
p = a[0]; /*等价于p = &a[0][0],但不能写成 p = &a[0],因为此时不是一个一维数组。*/
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
scanf("%d", p++);
p = a[0];
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
printf("%4d", *p++);
printf("\n");
}
return 0;
}
意义:一个数组,里面存放的全是指针
格式:类型说明符 * 数组名[数组长度]
如: char *str[4];
注意:
int *ptr[5]; /*指针数组*/
int (*ptr)[5];/*一个指针变量,指向数组*/
字符串常量:以双引号“”括起来的序列,C语言中称为字符串字面量。以 ‘\0’为结束的字符数组。
char ch;
ch = "abc"[1]; //ch的值为b
char digit_to_hex_char(int digit)
{
return "0123456789ABCDEF"[digit];
} /* 返回0-15对应的十六进制字符形式 */
惯用法:
#define STR_LEN 80
...
char str[STR_LEN+1];
int main()
{
int a;
char *s = "Hello World!";
printf("%s\n", &s[6]); /*打印处World,因为要填入一个字符串,这里后面有'\0',而且是一个地址*/
printf("%c\n", *s); /*打印,因为s是首地址,即H*/
return 0;
}
字符串变量:
由于字符串以数组的形式存在,所以下方传递
#include
int main()
{
char s1[10], s2[10];
printf("input the first string: ");
scanf("%s", s1);
printf("input the second string: ");
scanf("%s", s2);
if (s1 == s2) /*这里比较的是两个地址,所以输出一样的内容还是会直跳到else处*/
{
printf("string1 == string2\n");
printf("%s\n", s1);
printf("%s\n", s2);
}
else
{
printf("string1 != string2\n");
printf("the address of %s is %p\n", s1, s1);
printf("the address of %s is %p\n", s2, s2);
}
return 0;
}
char *s1;
scanf("%s", s1);
char *s2 = malloc((strlen(s1) + 1) * sizeof(char)); /*考虑'\0'*/
int n = sizeof(strlen(s1));
for (int i = 0; i < n; i++)
s2[i] = s1[i];
s2[n] = '\0'; /*需要手动在后面加上'\0'*/
char *str = "hello";
char word[] = "hello"; /*编译器会自动在后面加0*/
char word[10] = "hello";/*编译器会自动在后面加0*/
printf("hello""world");/* 两个双引号之间没有逗号是可以的,此时编译器会把两个字符串拼成一个字符串*/
printf("hello,\
world"); /*打印出来会带有一个table*/
printf("hello,\
world"); /*打印出来没有table*/
printf("%s", __func__);//返回函数名,两个下横线
字符串常量:
int i;
char *s = "hello";
char *s2 = "hello";
char s3[] = "hello";
printf("&i = %p\n", &i); /* &i = 000000000061FE0C */
printf("s = %p\n", s); /* s = 0000000000404000 */
printf("s2 = %p\n", s2); /* s2 = 0000000000404000 */
s3[0] = 'H'; /* 注意这里是单引号*/
printf("s3 = %p\n", s3); /* s3 = 000000000061FE06 */
printf("here, s3[0] = %c\n", s3[0]); /* here, s3[0] = B */
两个地址一样,是因为char *s 相当于 const char *s,由于历史原因,编译器接受不带const的写法。
putchar和getchar的使用
int ch;
ch = getchar();
while (getchar() != '\n')
;
while ((ch = getchar() == ' '))
;
字符串赋值:
char *t = "title";
char *s;
s = t;
/* 以上只是指针赋值,不是字符串赋值*/
字符串上输入输出:
char ch[] = "abcdefg";
printf("%.3s\n", ch); //只显示前3位
printf("%3s\n", ch); //显示完
printf("%10s\n", ch); //10个宽度,右对齐
printf("%-10s\n", ch); //10个宽度,,左对齐
gets和scanf的区别:前者遇到空格、table或换行均会结束;后者只会在换行符结束。
char string[8];
scanf("%s", string); /*数组名即指针*/
scnaf("%7s",string); /* 最多允许写入7个字符*/
printf("%.3s", string); /* 只显示前3个*/
注意点:
char buffer[100] = ""; /* 空字符串,bufer[0]='\0' */
char buffer[] = ""; /* 数组的长度只有1 */
char **a。a是一个指针,指向另一个指针,那个指针指向一个字符串。
char *a[]: 是一个数组,里面元素存放的是指针,每一个指针指定了一个字符串
char *a[] = {
"hello",
"world",
"!"
};
程序参数:
int main(int argc, int const *argv[])
{
}
int main(int argc, int const **argv) /*和上面的等价*/
{
}
// argv[0]是命令本身,后面是命令后的字符串
int main(int argc, char const *argv[])
{
for (int i = 0; i < argc; i++) {
printf("%d: %s\n", i, argv[i]);
}
return 0;
}
int count_spaces(const char s[]) /* 统计 有多少个空格*/
{
int count = 0;
for (int i = 0; s[i] != ' '; i++) /* */
{
count++;
}
return count;
}
/* ================================================== */
int count_spaces(const char *s) /* 用指针访问更方便。*/
{
int count = 0;
for (;*s != '\0'; s++) /* 可减少变量i */
{
if (*s == ' ')
count++;
}
return count;
}
先看一些错误范例:
char str1[10], str2[10];
str2 = str1; /* WRONG */
str2 = str1 = "abc";
if (str1 == str2); /* 比较是是指针,不是数组的内容 WRONG */
使用字符串函数时,应该在前面包含**#include
strcpy函数:
char *strcpy(char *restrict *s1, const char *s2); /* 函数原型,由此可知,是复制的是指针,不是数组本身。其中restrict表示不会重叠*/
/* 返回的是s1这个指针 */
strcpy(str2, "abcd");
strcpy(str2, str1);
常用套路:
char *dst = (char *)malloc(strlen(src) + 1); //分配空间,不要忘了+1
strcpy(dst, src);
while (src[idx] != '\0'){}
while (src[idx]){} /* 和上面等价*/
strlen函数:返回字符串长度,不包含末尾的‘\0’。
strcat函数:拼接两个函数,并且返回拼接后的指针。
strcpy(str1, "abc");
strcat(str1, "def"); //str1 = abcdef
strcat(str2, str1);
strcat(str1, strcat(st2, "ghi")) //利用返回值进行计算
strcmp函数:比较两个字符串的大小,根据两个字符串的大小返回一个大于、小于或等于0的值
int strcmp(const char *s1, const char *s2); /*函数原型*/
s1小于s2的情况:
用来把预处理指令替换成相应的代码,把宏替换,把注释去掉
#include:是一个预处理指令,和宏一样,编译之前就处理了。把文件的全部文本内容原封不动地插入到它所在的地方。
用#定义的语句,不需要用分号结束,因为这些句子不是C的句子。
#define PI 3.14159 //无分号
#define FORMAT "%f\n" //完全的文本替换也是可行的
printf(FORMAT, 2*PI);
#define PRT printf("%f\n", 2 * PI); \
printf("%f\n", 2 * PI) //这里没有分号
如果一个宏超过一行,那么最后一行前的行末需要加反斜杠\
注意,空格也会被当做宏,所以一行后不要带多余的空格
预先定义的宏:可用来检测错误或调试
__LINE__ //返回行号
__FILE__ //返回文件名
__DATE__ //返回日期(mm dd yyyy)
__TIME__ //返回时间(hh:mm:ss)
__STDC__ //如果编译器符合C标准,则返回1
__func__ //返回所在函数名
带参数的宏
#define cube(x) ((x)*(x)*(x))
printf("%d\n",cube(5));
#define MIN(a, b) ((a)>(b)?(b):(a)) /* 宏可以带多个参数 */
编写原则:
反例:
#define cube(x) (x*x*x)
cube(3+2) == (3+2*3+2*3+2) //运算不对
#if 整型常量表达式1 /*注意这里是常量表达式,不能是变量*/
程序段1
#elif 整型常量表达式2 /*#elif可省,如下*/
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
#include
int main(){
#if _WIN32
printf("This is Windows!\n"); /*识别出这里是windows系统*/
#else
printf("Unknown platform!\n");
#endif
#if __linux__
printf("This is Linux!\n");
#endif
return 0;
}
#ifdef 宏名
程序段1
#else
程序段2
#endif
#include
#include
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式编译程序...\n");
#else
printf("正在使用 Release 模式编译程序...\n");
#endif
system("pause");
return 0;
}
#include //<>让编译器只在指定的目录找
#include "max.h" /* 此为自己编写发头文件。“”让编译器先在当前目录(.c所在)找,找不到再去编译器指定的目录去找。 */
/* main.c */
#include
#include "max.h"
int main()
{
int a = 5;
int b = 6;
printtf("%f\n", max(a, gAll));
return 0;
}
/* max.h */
#ifndef _MAX_H_
#define _MAX_H_
double max(doublea, double b);
extern int gAll; //告诉编译器,在某个地方有个gALL
#endif
/* max.c */
#include "max.h"
int gAll = 12;
double max(double a, double b)
{
return a>b?a:b
}
变量的声明和定义
extern int i; //变量的声明
int i; //变量的定义
在max.h中,下面的语句是检查是否重定义的,建议所有的.h都包含这三句
#ifndef _MAX_H_
#define _MAX_H_
...
#endif
结构-成员:在其他语言种,成为记录-字段(record - field)
结构声明的3种形式
struct point{ //point表示结构标记
int x;
int y;
}; //注意这里的分号
struct point p1,p2; //p1,p2宝表示结构变量
struct {
int x;
int y;
} p1, p2; //注意这里的分号
struct point{ //第3种更常见
int x;
int y;
} p1, p2; //注意这里的分号
结构的成员在内存种是按顺序存储的
每个结构代表一个新的作用域,任何声明在此作用域的名字都不会和程序中的其他名字冲突。即每个结构为它的成员设置了独立的名字空间。
struct {
int number;
char name [NAME_LEN+1];
int on_hand;
} part1, part2;
/* ========================= */
struct {
char name [NAME_LEN+1];
int number;
char sex;
} employee1, employee2;
在上述两个结构中,part1和part2的成员number和name不会和employee1和employee2的成员number和name冲突。
结构的初始化
struct {
int number;
char name [NAME_LEN+1];
int on_hand;
} part1 = {528, "disk drive", 10},
part2 = {914, "printer cable", 5}; /* 初始化的值必须按照结构成员的顺序。未填写部分默认未0或空字符 */
/* 指定初始化 */
{ .number = 528, .name = "disk drive", .on_hand = 10 } //可以不按顺序
结构的操作
与数组通过下标操作相似,结构通过成员名来操作。
printf("Part name: %s\n", part1.name);
part1.number = 566;
part1.on_hand++; //结构的成员为左值
scanf("%d", &part1.on_hand); //.号的优先级比&高
part2 = part1; //把part1的所有成员对应赋值给part2
part2 == part1; //错误操作,不能这么比较!
结构类型的定义
typedef int number; /* 以前见过的类型定义,中间是原来的东西,后面是新的名词 */
typedef struct {
int number;
char name[NAME_LEN+1];
int on_hand;
} Part; /* 名字出现在这里,而不是跟在struct后面 */
一般来说,如果把一个结构当成参数来传入/传出函数,函数要生成结构中所有成员的副本。所有为了避免系统开销过大,一般使用指针作为参数
struct date {
int month;
int day;
int year;
} myday;
struct date *p = &myday; /* p为指针,myday前面必须要有&号。和数组不同*/
(*p).month = 12; /* (*p)必须要用括号括起来,因为.的优先级比较高 */
p->month = 12; /* 上一行的简便写法 */
#include
struct point
{
int x;
int y;
};
struct point* getStruct(struct point*);
void output(struct point);
void print(const struct point *p);
int main(int argc, char const *argv[])
{
struct point y = {0, 0};
getStruct(&y); //得到一个指向结构的指针
output(y);
output(*getStruct(&y));
print(getStruct(&y));
getStruct(&y)->x = 0; //(*p).x = 0
*getStruct(&y) = (struct point){1,2}; //结构初始化
return 0;
}
struct point* getStruct(struct point *p) /*套路:传入一个指针,利用它做了修改后再传回来*/
{
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("getStruct: %d, %d\n", p->x, p->y);
return p;
}
void output(struct point p)
{
printf("output: %d, %d\n", p.x, p.y);
}
void print(const struct point *p)
{
printf("print: %d, %d\n", p->x, p->y);
}
/* 如果r结构下面有pt1, pt2下面两个子结构 */
struct rectangle r, *rp;
rp = &r;
/* 那么以下四种形式等价 */
r.pt1.x;
(r.pt1).x;
r->pt1.x;
(r->pt1).x;
//不能用rp->pt1->x,因为pt1不是指针
结构的嵌套
struct person_name {
char first[FIRST_NAME_LEN+1];
char middle_initial;
char last[LAST_NAME_LEN+1];
};
struct student {
struct person_name name;
int_id, age;
char sex;
} student1, student2;
strcpy(student.name.first, "Fred");
结构数组:其元素为结构的数组,可构建简单的数据库。
格式:
struct part inventory[100];
print_part(inventory[i]); //访问存储在位置i的零件
inventory[i].number = 833; //赋值写法
inventory[i].number[0] = '\0'; // 名字变为空字符串
和结构类似,其成员可以拥有不同的类型。但编译器只为其最大的成员分配足够的内存空间。联合的成员彼此覆盖。例如下面的联合中,对i的改写会改变d,对d的改写会改变i。用途举例:比如一个选礼物的场景,每次只能选一个礼物。
union {
int i;
double d;
} u;
struct {
int i;
double d;
} s;
来源:一般来说,常量应该符号化,即1、2这些数字应该有具体的名字,以便阅读方便。所以解决办法有
cons int s=1; //法1
#define CLUBS 0 //法2
#define DIAMONDS 1
#define HEARTS 2
#define SPADES 3
但以上两种方法都不够好,所以催生了枚举类型(enumeration type)
enum suit {CLUBS, DIAMONDS, HEARTS, SPADES}; //默认第一个为的值为0,后面的加1
enum suit {CLUBS = 1, DIAMONDS = 100, HEARTS = 100, SPADES}; //可以自己修改里面的值
数组:在内存上是连续的区域。具有随机访问性。增加/删除元素比较麻烦
链表:往往不是连续的区域。由于必须由上一节点找出下一节点,所以不具有随机访问性。可方便插入和删除
struct node { //node为结构标记
int value;
struct node *next;
};
/* 在结构中有一个指向相同结构类型的指针成员时,要求使用结构标记 */
struct node *first = NULL; //链表初始为空
创建节点
struct node *new_node; //临时变量指向要创建的节点
new_node = malloc(sizeof(struct node)); //为新节点分配内存空间
(*new_node).value = 10; //把数据存到新节点中。注意圆括号
new_node->value = 10; //上一行的替代写法
插入节点
struct node *first = NULL;//链表初始为空
new_node = malloc(sizeof(struct node));//为新节点分配内存空间
nex_node->node = first; //先修改新节点的指针next,使其成为first指针NULL
first = new_node; //使first指向新节点
以上步骤可以写成一个函数
struct node *add_to_list(struct node *list, int n)
{
struct node *new_node;
new_node = malloc(sizeof(struct node));
if (new_node == NULL) {
printf("Error: malloc failed to add to list\n");
exit(EXIT_FAILURE);
}
new_node->value = n;
new_node->next = list;
return new_node; //没有修改指针,而是返回指向新产生的节点的指针
}
搜索链表:
for (p = first; p = != NULL; p = -> next)
{
...
}
惯用法:
struct node *search_list(struct node *list, int n) //搜索值为n的链表
{
struct node *p;
for (p = first; p != NULL; p = -> next)
{
if (p -> value == n)
return p; // 找到了就返回指针
return NULL; //没找到返回NULL
}
}
struct node *search_list(struct node *list, int n)
{
for (; list != NULL; list = list-> next) //除去p,用list来跟踪节点
if (p -> value == n)
return list;
return NULL;
}
struct node *search_list(struct node *list, int n)
{
for (; list != NULL && list->value != n; list = list-> next)
;
return list;
}
删除节点
每个函数在编译链接后总是占用一段连续的内存区,函数名就是该函数所占内存区的入口
格式:类型说明符 (*指针名)()
#include
int add(int a, int b);
int sub(int a, int b);
void result(int (*pf)(), int a, int b);
int main()
{
int a, b;
int (*pf)(); //定义一个指向函数的指针
printf("input two integer: ");
scanf("%d %d", &a, &b);
pf = add;
result(pf, a, b);
pf = sub;
result(pf, a, b);
return 0;
}
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
void result(int (*p)(), int a, int b)//这里的指针p没必要和pf相同
{
int value;
value = (*p)(a, b); //这里的指针p没必要和pf相同
printf("%d\n", value);
}
struct 位域结构名
{
type member_name : width;
};
元素 | 描述 |
---|---|
type | 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。 |
member_name | 位域的名称。 |
width | 位域中位的数量。宽度必须小于或等于指定类型的位宽度。 |
int main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */
pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}
以??后面加一个字符的形式,在打印时会显示成另外的样子。MISRA-C里面规定不能使用三字母词
三字母词 | 输出 | 三字母词 | 输出 | 三字母词 | 输出 |
---|---|---|---|---|---|
??= | # | ??( | [ | ??) | ] |
??< | { | ??> | { | ??/ | } |
??! | | | ??’ | ^ | ??- | ~ |
符号 | 含义 | 符号 | 含义 |
---|---|---|---|
& | 按位与 | | | 按位或 |
~ | 按位取反 | ^ | 按位异或 |
<< | 左移 | >> | 右移 |
按位与:让某一位或某些位为0,如用FE(11111110可以使最后一位为零);取其中一个数的一段,用FF
按位或:让某一位或某些位为1;使两个拼接,如0x00FF | 0xFF00
使用这些符号后,所得是int型。如果打印,需要用%d等
按位异或:x^y^y = x,即同一个变量用同一个值异或两次,等于本身。可用来加密
左移:左移后右边填入零,相当于乘2。所有小于int的类型,移位以int的方式来做,结果是int。
右移:相当于除以2。所有小于int的类型,移位以int的方式来做,结果是int。对于unsigned类型,左边填入0;对于signed类型,左边填入原来的最高位(保持符号不变)。
举例:
SID = (bit)(i_data & 0x80) /* 后面&号表示取出字节的最高位,后7位均为0,加上(bit)表示强制转换为一个位*/
i_data &= 0xf0; //取出字节的高4位
i_data <<= 4; //左移4位,即选出了字节的低4位
int main()
{
char c;
do
{
printf("Uppercase character please: ");
scanf("%c", &c);
} while (c < 'A' || c > 'Z');
printf("%c\n", c | 0x20); /*ASCII码中大小写相差32,如a是97(01100001),A是65(01000001)*/
//printf("%c\n", c & 0xDF); /*这里是小写转大写的方法*/
return 0;
}
void swap(int *a, int *b) /*不用临时变量完成两个数的交换*/
{
int *a, *b;
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
swap(&x, &y); /*调用*/
void foo(void)
int main(int argc, char *argv[])
{
foo();
}
void foo(void)
{
foo();
}