1. C语言基础
  2. 乘法逆元
  3. 信息安全算法基础
  4. 操作系统基础
  5. x86汇编基础
  6. 信息论与编码

变量存储

全局变量与局部变量

全局变量的特点:

  • 程序块之外的算全局变量(常常放在 main 函数之前),但是有先后识别顺序。

  • 全局变量适合给函数直接传参,但是程序运行的时候都占内存,而且每一部分具有依赖性,迁移性差。

  • 大量的全局变量使得牵扯太多,可读性差。

extern 标识符:

  • 标识着这个变量或者函数要在其他的文件中寻找,即在别处定义,此处需要引用,在 gcc 编译中跨文件变量的使用很有用。
  • extern 外部变量声明(要区分定义和声明),忽视读取先后顺序。
1
2
extern int a,b;
//这里不分配内存,是标志着后面会定义,定义的时候分内存。
  • extern 在局部中声明,只对这一部分的代码块有效。

  • 如果和代码块的局部变量重名,那么这个代码块不影响这个全局变量,按照里面的局部变量处理。

  • 全局变量跨文件引用,默认是不支持的,但是可以在本文件里声明了 extern 后,就不会分配内存,可以引用了。(注意,这样交叉使用,可读性会很差)

变量的存储和使用

静态存储方式和动态存储方式

静态存储方式:分配固定存储空间。

全局变量放在静态存储区,程序结束后才释放。

动态存储方式:根据需要动态发分配存储空间。

形式参数、局部变量和函数的返回值以及被调用时的值存储在动态存储区。

static

设置局部静态变量,一般而言函数或者程序块调用完了,内存就会销毁。但是设置 static 声明局部静态变量,保存原来的内存,下次继续使用,而且只在编译时赋值一次。如果局部静态变量不赋初值,那么默认为 0

如果用在全局变量之前,表示只能在本文件中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
void functest() {
static int c;
printf("%d\n", c);
c++;
return;
}

int main() {
functest();
functest();
functest();
functest();
}

注意它仍然是局部变量,其他的程序块不能引用。而且,声明了之后 static 就不能跨文件引用了

外部函数和内部函数

标准是能否被其他源文件调用。

内部函数:只能在本文件里使用。只需要在函数前面加上 static,因此也被称为静态函数

外部函数:不用 static 就是外部函数。默认支持函数跨文件引用。也有在前面加 extern 来声明的,但是着不必要。只需要在前面加函数声明即可。

预编译

不带参数的宏定义

定义的格式#define 宏的名字 字符串

终止的格式#undef 宏名

好处:用简单的名字代替复杂的字符串,在修改的时候非常好用,也提高了可移植性。
//在编译的时候替换的过程,就叫做宏展开。

1
2
#define PI 3.14151926
#undef PI

说明:

  1. 宏名一般用大写字母,这是习惯
  2. 不是 C 语句,不要加分号,否则分号会被一起替换
  3. 不能跨文件使用。
  4. 宏定义中可以使用其他宏。
  5. 双引号(字符串)内的宏名不替换,

带参数的宏定义

格式:#define 宏的名字(参数表) 字符串,#define S(a,b) a*b

  1. 只是替换,不会做其他处理。

    S(1+5,2+3) 表示 1+5*2+3,计算结果不是原来的求积
    所以常常加括号处理,#define S(a,b) (a)*(b)

  2. 复杂语句中有时候使用多行语句进行宏代替

    1
    2
    3
    #define PRASE do{\
    printf("test/n");\
    }while(0)
  3. 宏展开是在编译时进行的,展开时不分配内存,也没有返回值这种说法,也没有值传递的说法。

  4. 宏替换只占用编译时间,不占用运行时间。而函数调用占的是运行时间(分配内存,传递参数,执行函数体,返回值) ;

文件包含

本质上:将另外文件中的代码包含在这个文件中

格式:#include “文件名” 其中文件名常常为 .h 文件,叫做头文件

  1. 一个 include 只能包含一个文件。
  2. 可以嵌套包含。一个头文件中,还能包含其他头文件。
  3. 方便使用现成的代码,也方便公共修改。
  4. <>表示到系统目录找文件,“ ”表示首先在当前目录查找,找不到再到系统目录找。

条件编译

格式:

方式一:

1
2
3
4
5
6

#ifdef 标识符 //如果定义过这个标识符,则执行这一段。
程序段
#else
程序段
#endif

方式二:

1
2
3
4
5
#ifndef 标识符 //如果没有定义过这个标识符,则执行这一段。
程序段
#else
程序段
#endif

//方式三

1
2
3
4
5
6
7
#if 表达式 //如果表达式为真,则执行这一段。
程序段
#elif 表达式
程序段
#else
程序段
#endif

优点:

  1. 编译的时候只剩下满足条件的那一块,可以减少文件长度。
  2. 跨平台时,条件编译时预设好不同平台需要编译的代码。
  3. 方便测试,只需要删除宏定义就可以不执行特定代码块。

指针

指针非常重要!指针式 C 语言的重要特点,效率非常高,但是非常灵活。

前提知识

认识

  1. 变量可能保存在不同的存储区,比如静态存储区、动态存储区等。
  2. 变量分配内存的时间不同,有些事编译的时候分配内存,有些则是运行时分配。
  3. 每种数据类型都会占用内存。例如 int, char, float 等,可以用 sizeof()查看。

地址的概念

计算机中用十六进制的数来表示地址,0x 开头,表示十六进制。

**严格区分地址和地址代表的内容。**一个地址代表一个字节,但是我们以第一个占用的地址为这个变量对应的地址。程序内部维持一张表,记录着地址和变量名的对应关系。

直接访问和间接访问

直接访问:按地址取址,直接从地址中存取变量。

间接访问:将变量的地址存放在另一个内存单元中,即一种特殊变量来存储地址。例如:p=&a,&是地址符号。这就是 p 的地址指向了 a 的地址,而 P 的地址一般使用四个字节的内存,里面储存着 a 的地址。

间接访问的读取的过程

  1. 先找到存放 p 的地址,然后从 p 的地址中取出 a 的地址。
  2. 然后从 a 的地址中找到存储的内容。
  3. 通过映射表来把存储的内容映射成 a.

我们就说像上面 p 那样,专门存放另外一个变量的地址的变量,就叫做指针变量。指针(地址)的值就是指向的变量的地址。

指针变量

格式:类型标识符号 *指针变量名

1
2
3
4
5
6
7
int i,j;
float k;
int *mypoint1,*mypiont2; //int 类型变量表示,我们要指向的是一个整型变量.
float *mypoint3; //表示指向浮点数类型的指针变量。
//给指针变量赋值
mypoint1=&i;
mypoint3=&k;
  1. 定义指针变量的时候要加*,但是使用的时候不加*,指针变量名是不含*的。
  2. 指针变量只能指向同一类型的变量。
  3. 指针变量只存地址,不要乱赋值。

运算符:&:取地址运算符。

*:间接访问运算符。

如果不是定义指针变量或者当作乘法运算,*指针变量名 表示所指向的变量

注意事项:*p=a

  1. &*p。因为指针运算符和乘法运算符的优先级相同,而且是从右至左结合。所以相当于&(*p),即 p 指向的变量的地址,相当于 p。
  2. *&a。&a 表示 a 的地址,也就是 p 的值,而*p 即指向的变量,相当于 a。
  3. (*p)++。相当于 a++。
  4. *p++。因为++和*的优先级相同,而且是从右到左运算,所以等价于*(p++),而指针变量加一,代表跳过存储这一块内存占用的地址,到下一块内存。比如 int 类型占用 4 个字节,那个 p++的值加 4.但是,在引用的时候,*p++ 是先用后加,返回值是*p,然后 p++
  5. *++p。先加后用,返回值是*(p+1).

指针赋值代表指向传递

数组的指针和指向数组的指针

特点:

  • 数组里面的元素的地址是相连的。

  • 数组的地址是第一个元素的地址。

  • 数组名就代表数组地址。

定义指针变量并赋初值:

1
2
3
4
5
6
int *p=&a[0];
//等价于
int *p=a;
//等价于
int *p;
p=&a[0];

通过指针引用数组元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//第一类,赋值
*p=19;

//第二类,访问下一个内存单元
p=p+1;

//第三类,访问特定元素地址
//p+i或a+i 指的是a[i]的地址

//第四类,访问特定元素的值
//*(p+i)或者*(a+i) 指a[i]

//第五类,访问特定元素的值
//p[i]与*(p+i)等价,即a[i]

a[i],p[i],*(P+i),*(a+i),都代表引用数组元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
int main() {
int a[5] = { 12,14,20,18,50 };
int* p;
int i;

for (i = 0; i < 5; i++) {
printf("%d\n", a[i]);
}
printf("------------------------------------\n");
for (i = 0; i < 5; i++) {
printf("%d\n", *(a+i));
}
//效率最高
printf("------------------------------------\n");
for (p = a; p<a+5; p++) {
printf("%d\n", *p);
}

实际上,系统中是根据 a[i],推算*(a+i),所以通过指针来引用,效率是比较高的。

注意

  • 数组首地址不能更改。a++不合法。
  • 不要动自己未定义的内存。

数组作为函数参数

  1. 实参形参都是数组名,那么函数就可以改变实参数组的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
void changeValue(int ba[]) {
ba[3] = 27;
ba[4] = 45;
return;
}
int main() {

int a[5] = { 85,70,98,92,78 };
printf("%d %d %d %d %d\n",a[0],a[1],a[2],a[3],a[4]);
changeValue(a);
printf("%d %d %d %d %d\n", a[0], a[1], a[2], a[3], a[4]);
printf("断点");
}

其中,a,ba 公用同一段内存,指的是同一个数组。

  1. 实参是数组名,形参是指针。

这时赋值时和两者都是数组名,几乎一样。

  1. 实参和形参都是指针变量

这时赋值时和两者都是数组名,几乎一样。

  1. 实参为指针,形参为数组名
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
void changeValue(int ba[]) {
ba[3] = 27;
ba[4] = 45;
return;
}
int main() {

int a[5] = { 85,70,98,92,78 };
int *pa = a;
printf("%d %d %d %d %d\n",a[0],a[1],a[2],a[3],a[4]);
changeValue(pa);
printf("%d %d %d %d %d\n", a[0], a[1], a[2], a[3], a[4]);

这几种方法很类似,指针数组作为参数传入,也会被当作数组。

多维数组

多维数组在内存中是连续存放的,注意顺序,a[3][3][4],从右边开始变,且递增。下边这样变化,000,001,002,003,010 …213,213,220,221,222,223。

image-20210307133540210

注意:

  1. a+i,指的是 a[i]行的内容。
  2. a[0],代表的是地址,即 a[0]=&a[0][0].
  3. a[0]+1,代表在 a[0]这一行,a[0][1]的地址,即 a[0]+1=&a[0][1].
  4. *a=a=a[0],所以 a[0]+1=*a +1.
  5. *(a+1)+2,指第一行第二列的地址。

核心规律:

二维数组的地址是地址指向地址,一层一层嵌套,*表示进入当前这一层嵌套,a[]也表示进入了这一层的嵌套。

指针数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
int main() {
int *p[4];
int a[3][4];
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 4; j++) {
a[i][j] = 86;
}
}
p[0] = &a[0][0];
p[1] = &a[0][1];
p[2] = &a[0][2];
p[3] = &a[0][3];

for (j = 0; j < 4; j++) {
printf("%d\n", *p[j]);
}
printf("断点");
}

数组指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int main() {
int(*p)[10]; //长度为10个整型的一个地址,这样方便递增改变地址。
int a[10];
int i;
for (i = 0; i < 10; i++) {
a[i] = i;
}
p = &a; //一维数组需要有寻址符号。
int *q;
q = (int*)p;
for (i = 0; i < 10; i++) {
printf("value = %d\n", *q);
q++;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
int main() {
int(*p)[10];
int a[3][10];
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 10; j++) {
a[i][j] = i + j;
}
}
p = a;
int *q;
q = (int*)p;
for (i = 0; i < 3; i++) {
q = *(p + i);
for (j = 0; j < 10; j++) {
printf("%d ", *q);
q++;
}
printf("\n");
}
//这一段循环也可以这样写
//for (i = 0; i < 3; i++) {
// q = *(p + i);
// for (j = 0; j < 10; j++) {
// printf("%d ", *(*(p+i)+j));
// q++;
// }
// printf("\n");
//}
}

字符串的指针

字符串的表现形式

1
2
3
4
5
#include<stdio.h>
int main() {
char str1[] = "I love you!";
printf("%s\n", str1);
}

字符数组的指针规律不变

因为字符数组实际上是转码后拷贝到str1[],分配不同的内存,和数字的数组是一致的。

常量的字符指针

因为内存中有一段类似字符数组的东西存放字符串常量,所以 指向同一个字符串常量的指针是相同的

这样很方便灵活的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <windows.h>
int main() {
char a[1000];
int i, n = 0,d=1;
int N = 10;
while (1) {
for (i = 0; i < n; i++) {
*(a + i) = ' ';
}
*(a + i) = 'A';
*(a + i + 1) = '\0';
printf("%s", a);
system("cls");
n += d;
if (n == N||n==0) {
d = -d;
}
}
}

字符指针做函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
void copystr(char *from, char *to) {
int i;
for (i = 0; from[i] != '\0'; i++) {
to[i] = from[i];
}

/*while (*from) {
*to++ = *from++;
}*/

to[i] = '\0';
}
int main() {
char a[] = "this is source content.";
char b[100];
copystr(a, b);
printf("%s\n", a);
printf("%s\n", b);
}

区分字符指针与字符数组

  1. 字符数组是由若干个元素组成,每个元素中存放一个字符;字符指针存放的事字符串的首地址,不存放内容。
  2. **赋值方式的差别:**初始化字符数组时,是把常量拷贝给字符数组。字符指针初始化时,是把指针指向常量的地址。
  3. 字符数组的地址不可以更改,但是字符指针可以再次定义指针的指向。

函数指针

函数指针的用法

当编译的时候,函数就会分到一个地址,那么通过调用这个地址,就可以调用这个函数。

**格式:**数据类型标识符 (*指针名)(形参列表)

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int max(int x, int y) {
if (x > y)
return x;
return y;
}
int main() {
int (*p)(int x, int y);
p = max;
printf("%d", (*p)(14, 9));
}

注意:

  • *p两边的括号不能省略,括号优先级比指针高。
  • 函数名代表函数的入口地址,p = 函数名表示p也指向函数的开头,p=&max也可以
  • p不能指向语句,所以p+1是不合法的。
  • 函数调用的时候,*p可以写成p,即p(14,9)
  • 在某些编译器中函数的入口地址和实际地址不一样,编译器会把入口地址映射到其他地址,在使用的时候使用实际地址,所以可能出现函数地址和指向函数的指针不一样的情况。

函数指针作为函数参数

指向函数的指针也可以作为另一个函数的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int max(int x, int y) {
if (x > y)
return x;
return y;
}
int wwmax(int x, int y, int (*midfunc)(int x, int y)) {
int result = midfunc(x, y);
return result;
}
int main() {
int (*p)(int x, int y);
p = max;
int c = wwmax(5, 19, p);
printf("%d", c);
}

返回指针值的函数

格式 数据类型 *函数名(参数列表),如 *p(int x, int y),因为括号的优先级比指针符号高,所以相当于

*(p(int x, int y)),它指向函数的首地址,里面既有可引用执行的函数也有函数的返回值,。

注意不要和函数指针弄混了

注意,绝对不可以读写被回收的地址,这种错误在引用函数中的局部变量时特别容易出现

指针数组、多重指针、main 函数参数

写法与区分

指针数组中的每一个元素都是指针类型的数据,都是指针变量。例如int *p[4],注意区分int (*p)[4],前者的每个元素都是指针,而后者的每个元素都是整型,方便存储地址和引用这个地址(因为(*P)[i+1]*(p+1),是等效的。

用途:多个字符串

因为每个常量都是有固定的地址的,我们就用指针数组来指向这个地址,实现存储多个字符串。

1
2
3
4
5
6
7
8
9
10
int main() {
char *p[] = { (char*)"python",(char*)"C++",(char*)"java",(char*)"Go" };
int len = sizeof(p) / sizeof(p[0]);
char* tem = p[0];
p[0] = p[1];
p[1] = tem;
for (int i = 0; i < len; i++)
{
printf("p[%d]=%s\n", i, p[i]);
}

指向指针的指针

int **pchar **P因为指针符号是从右到左结合,所以相当于int *(*p),表示p是指向一个指针变量,*pp指向的指针变量。

int a = 5;
int* p =&a;
int ** pp=&p;
printf("%d %d %d", a, *p, **pp);

注意这里int* p =&a;,实际上是int* p;p =&a;,也就是p接收到的是地址。

用途

用于设置多维数组比较方便,尤其是可变长度的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void move(char b[],int row,int col)
{
char** a = (char**)calloc(row,sizeof(char **));
int q = 0;
for (int i = 0; i < row; i++)
{
a[i] = (char*)calloc(col,sizeof(char *));
for (int j = 0; j < col; j++)
{
a[i][j] =b[q++] ;
}
}
}

在传参的时候,将多维数组转化成一维数组,然后再这样把一维数组转化成多维数组。

指针做 main 函数的形参

我么使用 main 函数时常常是把他的参数为空,如果传入参数

1
in main(int argc, char *argv[])

那么默认argc等于 1,argv[0]默认是可执行文件的绝对路径。

另外如果再编译器中设置或者是是在命令行中设置

image-20210327100758690

右击项目名,属性,配置属性,调试,命令参数。在这里可以加入字符串,注意用空格区分不同的字符串。

这时argc的个数会增加,argv会多存储这几个字符串。

也可以在 shell 里执行文件时 添加参数

回顾

指针变量的数据类型区分

image-20210327103118880

指针变量运算

指针变量的加减是跳到相连的存储单元。

指针变量赋值不能够自己赋值给指针变量

空指针默认是 NULL,就是整数 0,而系统不会在地址为 0 处存在有效数据。默认赋空值是个好习惯。

万能指针

void*p表示万能指针变量,可以指向任何类型的指针

结构体和共用体

为了将多种数据类型整合起来,我们引入结构体。结构体实际上是一种类型。

定义结构体类型变量

格式:struct 结构体名{成员表列};

1
2
3
4
5
6
7
8
struct student
{
int num;
char name[100];
int sex;
int age;
char address[100];
};

**方法一:**把结构体当成数据类型,常规定义变量

struct student s1,s2;

**方法二:**定义结构体的同时,定义变量;

1
2
3
4
5
6
7
8
struct student
{
int num;
char name[100];
int sex;
int age;
char address[100];
}s1,s2;

**方法三:**直接定义结构体类型的变量,定义时省略结构体名,但是后续不能够再定义这样的的结构体类型的变量了。

1
2
3
4
5
6
7
8
struct
{
int num;
char name[100];
int sex;
int age;
char address[100];
}s1,s2;

注意:

  • 一般是使用方法一,分两步,先定义结构体,再定义结构体变量

  • 结构体类型可以嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	struct student
{
int num;
char name[100];
int sex;
int age;
char address[100];
struct date birtday;
};
struct date
{
int day;
int month;
int year;
}
  • 结构体内的变量名不影响程序中的变量名

引用结构体类型变量

  • 不能将结构体变量作为整体引用,只能对结构体中的成员分别引用。

    例如:s1.num=1;表示将 1 赋值给s1变量中的成员num,注意结构体成员运算优先级非常高,和括号是平级的.

  • 如果成员本身又属于结构体类型,就需要一步一步的找到最低级的成员。例如s1.birthday.day

  • 成员变量当成普通变量,成员变量也是有地址的,各种运算都是一致的。例如s1.num++,但是s1++是不允许的。

结构体变量的初始化

定义时按顺序赋值,例如s1={19,Bob,1,18,"main street 503",4,5,2002}

如果时数组的话就像一般的数组一样来初始化。

1
2
3
4
5
struct student stu[3]={
{},
{},
{}
};

数组的引用和一般的数组也是一致的。

结构体的指针

指针的类型由结构体确定 struct student *p

给成员赋值

**方法一:**指针得到地址,然后用指针符号和指针名代替变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<string.h>

struct student
{
int num;
char name[100];
int sex;
int age;
char address[100];
struct date birthday;
};
struct date
{
int day;
int month;
int year;
};
int main() {
struct student stu;
struct student* p;
p = &stu;//注意需要加地址符号,因为这个指针,需要指向地址
strcpy_s(stu.name, "Bob");
(*p).age = 20;
}

**方法二:**使用->,指向结构体成员运算符。实际上我们不用(*p).age = 20;的方式来用指针赋值,而是p->age=20,注意 arrow operator 的优先级非常高,也可以整体看成一个变量。

指向数组

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
struct student stu[2] =
{
{1,"张三",1,39,"北路90号",4,10,1902},
{23,"张四",0,19,"南路0号",6,11,1992}
};
struct student* p;
p = stu;//这里不需要地址符号 //也注意不要指向成员。
strcpy_s(stu[1].name, "Bob");
//(*p).age = 20;
p++;//表示从stu[0]跳到stu[1]
p->age = 20;
}

指向结构体的指针作为函数参数

因为时指向指针的,因此直接改变内存中的值,不过,如果是传入结构体,就不会改变原来地址中的值

共用体

把几种不同类型的变量存放在同一段内存单元,每次赋值都会清空原来的内容,赋值上新的内容。因此每个时刻只有一个成员起作用。

形式:union 共用体名 {成员列表}变量列表;和结构体的形式除了标识几乎一样。但是共用体占的内存是最长的成员的内存,而结构体占的内存是所有成员的和。

注意:

  • 共用体的地址和每个成员的地址相同。
  • 共用体不能在定义时初始化。
  • 不能把共用体变量作为函数参数。

枚举类型

用枚举类型可以使分类分别用整型标识,这样非常明确类型的含义。实际上枚举这个命名并不准确,更准确的是一一对应的常量。

定义类型:

1
2
3
4
enum color
{
red,green,blue,yellow
};

定义变量:enum color c1,c2;

  • 可以直接定义枚举变量

  • red,green,blue,yellow叫枚举常量,当作整型,值不可改变,默认从 0 开始,也就是分别对应0,1,2,3

  • 枚举型变量可以用枚举型常量赋值c1=red;

  • 定义枚举常量时,可以特定的赋值,后面的自动加一。

    1
    2
    3
    4
    enum color
    {
    red=-8,green,blue=10,yellow
    };
  • 可以强制用数字赋值c1=(enum color)1;

  • 枚举常量可以当作整型做任何整型可以做的操作。

定义类型别名

typedef 原来的类型名 别名

  • 别名一般用大写字母
  • 编译时处理
  • 用于定义类型名,而不是定义变量。
  • 作用主要是在程序的通用性和可移植性,例如需要改类型,那么就只要改typedef这一行。

简单定义

typedef int INEGER,这样就给 int设置了一个别名

定义结构体的别名

,例如下面定义坐标

1
2
3
4
5
typedef struct point
{
int x;
int y;
}COOR;

定义数组别名

定义元素为 100 的整型数组typedef in NUM[100],使用时NUM n;,那么就相当于int n[100]

定义字符指针

typedef char *PSTING,使用时PSRING p,q;,就相当于char* p,q;

实际上,先写常规的类型名字,然后把变量名替换成别名,在前面加上typedef,然后就可以用这个别名代表这个类型。

介绍

一个字节由 8 个二进制位,最左边的称为二进制位,最右边的称为最低位。

位运算是针对二进制数字而言的

  • & :相同的位置,只有都为 1 才为 1。

  • |:相同位置,只要有一个为 1,就为 1

  • ^:(亦或)相同位置相同才为 1

  • ~:单目运算符。按位取反,0 变成 1,1 变成 0.

  • <<:将二进制位左移若干位,右侧补 0。反映在十进制上,是数 ×2

  • >>:右移。超过的最低位会被舍弃,相当于 ÷2.

    &=,|=,^=,~= 也可以作为符合运算符使用,和+=,-=类型

应用

主要是在密码学和算法中用到比较多。

下面是一个其他的简单例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#define BIT(x) (1<<(x)) //1左移x位
unsigned int task=0;
enum EnumTask
{
ETask1 = BIT(0),
ETask2 = BIT(1),
ETask3 = BIT(2),
ETask4 = BIT(3),
ETask5 = BIT(4),
ETask6 = BIT(5),
ETask7 = BIT(6),
ETask8 = BIT(7),
ETask9 = BIT(8),
ETask10 = BIT(9),
};

int main() {
if (task & ETask7 printf("the task has already finished.");//判断任务7是否做过
task=task|Etask7;//其他位不变,任务7的位置变成1.
}

文件的读取与输出

实际上在计算机中都是二进制来储存。

文件在读和写之前都要打开,使用完了要关闭。

fopen_s(文件指针的指针,文件地址,打开模式)例如:

1
fopen_s(&stream, "H:\\data.txt", "r"); //以data.txt文件为例

fputc(字符,文件指针) 写入一个字符如果失败返回 EOF,end of file 值-1

fgetc(字符,文件指针) feof(fp)如果是文件末尾就返回 0。

fgets(字符数组,字节上限,文件流指针):如果有回车就停止。

fprintf(文件指针,),fscanf()

fweite(接收的指针,每个单元字节数,单元数,文件指针)

区分w,a,r的模式

函数

字符串赋值:

memcpy(数组a的地址,数组b的地址,操作的字节数):其中操作的字节数常用k*sizeof(a[0])表示复制前面 k 个元素,而sizeof(b),就是把b的所有内容都复制过来,要注意不要使用非法内存。

strcpy(a,b):将 b 的内容复制给 a,其中 b 常常是常量字符串。

memset(a, 0, sizeof(a));:对数组进行初始化赋值 ,用法和上个函数类似,原理也是对内存操作。

sprintf(字符数组的地址,“格式化”,对应变量):输出到字符串。

字符串比较:

strcmp(a,b):相同则返回 0,a的字符串大于b,就返回大于零的数。(即从第一个开始比较,相同字符的减去,然后比较下一个对应的字符,返回a中这个字符的 ascii 码与b中对应位置字符的 ascii 码的差值)

字符串拼接:

strcat(a,b):将b的内容拼接到a的后面,b自身不变。

查找字符串内容

char * strchr (const char *str, int c): 返回 str字符串中第一次出现的字符 c 的地址,如果没有这个字符则返回 NULL。

字符属性

int isalnum(字符):是否是数字或字母,是的话则返回非 0 的数,否则返回 0;

int isalpha(字符):是否为字母。

int isdigit(字符):是否为十进制的数字。

int ispunct(字符):是否是标点符号。

int islower(字符):是否是小写字母。

int isupper(字符):是否是大写字母。

字符转换

int tolower(字符):成功则返回非零,否则为 0。

int tolower(字符):成功则返回非零,否则为 0。

小技巧与知识

  • !0=1,!1=0,所以可以用 0 和 1 相对方便的表示出布尔型变量,而且适合在成双成对时进行间隔的操作int a=1;a=!a;
  • a==0或者a==NULL时执行操作,等价于!a,如if(a==NULL)等价于if(!a)
  • 如果是简单映射,可以用常量数组来完成。
  • 整个地使用字符串用指针,使用多个字符串用指针数组。