C语言基础

C语言定义变量

定义一个变量

int price;

给变量赋值

price = 10;

定义一个变量的同时给变量赋值

int num = 10;

C语言使用变量之前必须赋值或者初始化

试试只定义变量是什么情况

int i;
printf("%d\n", i);
结果
4203721//也可能是任意其他的值

定义变量i,会在内存中为这个变量i开辟出一块区域,如果不初始化,那使用的就会使这个区域上原来的垃圾值

C语言中定义静态变量或者全局变量不赋值默认会给0值,但是局部变量不赋值会使用该变量内存区域上的垃圾值。

静态变量使用static关键字来修饰,静态变量的存储方式与全局变量相同,都是静态存储方式。注意静态变量属于静态存储方式,属于静态存储方式的变量不一定就是静态变量。例如:全局变量虽然属于静态存储方式,但并不是静态变量,它必须由static修饰才能成为静态变量

关于为什么C语言中静态变量的初始值是0?请看知乎回答

https://www.zhihu.com/question/49111720

同时定义、初始化多个变量

int a,b=10,c;//起中a和c的值不确定b的值为10
int d = 0, e = 0;//初始化多个变量只能一个一个赋值

C语言定义常量

常量必须初始化,在之后的程序就中不可以修改常量了

const int AMOUNT = 10;

使用scanf来获取输入

int a
scanf("%d", &a)
printf("%d\n", a)

浮点数

整数运算的结果一定也是整数,比如5/2=2...1,那C语言会直接把余数丢掉

printf("%d\n", 10/3*3);
//结果是9,明显不对

//改进--使用浮点数
printf("%f\n", 10.0/3);
//结果是3.333333
printf("%f\n", 10.0/3*3);
//结果是10

浮点数的类型有两种:单精度float、双精度double

整数和浮点数的输入输出

int:
printf("%d",...);
scanf("%d",...);

double:
printf("%f",...);
scanf("%lf",...);//注意输入时lf

浮点数例子:英尺转米

printf("请分别输入身高的英尺和英寸,"
        "如输入\"5 7\"表示5英尺7英寸:");
int foot;
int inch;
scanf("%d %d", &foot,  &inch);
printf("身高是%f米。\n",
        ((foot + inch / 12) * 0.3048));
结果
1.524000米//错误的结果

改进

printf("请分别输入身高的英尺和英寸,"
        "如输入\"5 7\"表示5英尺7英寸:");
double foot;
double inch;
scanf("%lf %lf", &foot,  &inch);
printf("身高是%f米。\n",
        ((foot + inch / 12) * 0.3048));

练习:计算时间差

int hour1, minute1;
int hour2, minute2;
printf("请输入两组时间,比如1:30和 2:45\n");
scanf("%d:%d", &hour1, &minute1);
scanf("%d:%d", &hour2, &minute2);
int m1 = hour1*60 + minute1;
int m2 = hour2*60 + minute2;
int c = m2 -m1;
printf("时间差为%d时%d分\n", c/60, c%60);

C语言的赋值运算符=

在其它语言里=可能是个语句,但是在C语言里=是一个运算符,运算符是一个有结果的,a=6;的结果就是6

if(a=6){
    ...
}
//就相当于
if(6){
    ...
}

C语言中还有许多其他的运算符比如+、 -、 *、 /、 %、 +=、 -+、 *=、 /=、>、<、>=、<=、==、!=等等,这些运算符的作用还有优先级可以百度查查

小tips:交换两个变量

int a = 5;
int b = 6;
int t;
t = a;
a = b;
b = t;
printf("%d %d\n", a, b);

像类似这种交换变量值的代码,都是一些小套路,光学习语言的语法还不够,像这些小套路小tips也是要多多学习的。那么多阅读别人的代码来学习喽~

练习:逆序输出数字

712 / 100 -> 7
712 % 10  -> 2
712 % 100 -> 12 / 10 -> 1
712 / 10  -> 71 

练习:计算时间差2

int hour1, minute1;
int hour2, minute2;
printf("请输入两组时间,比如2:45和 1:40\n");
scanf("%d:%d", &hour1, &minute1);
scanf("%d:%d", &hour2, &minute2);
int im = minute2 - minute1;
int ih = hour2 - hour1;
if (im < 0) {
    im = 60 + im;
    ih--;
}
if(ih < 0 && im > 0){
    ih++;
    im = im - 60;
    im = -im;
}

printf("时间差为%d时%d分\n", ih, im);

关系运算符>、<、>=、<=、==、!=

关系运算符的运算结果为0或者1

printf("%d\n", 4==3);//0
printf("%d\n", 4==4);//1

练习:找零计算器

int price;
int bill;
printf("请输入价钱:");
scanf("%d", &price);
printf("请输入票面:");
scanf("%d", &bill);
if (bill > price) {
    printf("应该找您:%d", bill - price);
} else {
    printf("钱不够");
}

switch-case

switch (表达式) {//表达式的结果必须为整数
    case 1:
        printf();
        break;
    case 常量:
        ...
        ...
        ...
    default:
        break;
}

计算正整数的位数(do-while,while)

int x;
int n = 0;
printf("请输入一个正整数\n");
scanf("%d", &x);
do{
    x /= 10;
    n++;
} while (x>0);
printf("%d\n", n);

练习:逆序输出正整数

int x;
int digit = 0;
int ret = 0;
printf("请输入一个正整数\n");
scanf("%d", &x);
while(x>0){
    //取出个位数 
    digit= x % 10;
    ret = ret * 10 + digit;
    //丢掉个位数 
    x /= 10;
} 
printf("%d\n", ret);

阶乘(for)

1*2*3*4*5...

int n;
scanf("%d", &n);
int fact = 1;

int i = 1;
while ( i <= n) {
    fact *= i;
    i++;
}
printf("%d!=%d\n", n, fact);
int n;
scanf("%d", &n);
int fact = 1;
for (int i=1; i <= n; i++) {
    fact *= i;
}
printf("%d!=%d\n", n, fact);

跳出多重循环

接力break,或者使用goto,但是建议只在跳出多重循环的场合使用goto,因为goto容易破坏代码结构

int x;
int one, two, five;
int exit;
scanf("%d", &x);
for (one = 1; one < x*10; one++) {
    for (two = 1; two < x*10/2; two++) {
        for (five = 1; five < x*10/5; five++) {
            if (one + two*2 + five*5 == x*10) {
                printf("....");
                exit = 1;
                break;
                //goto out;
            }
        }
        if (exit) break;
    }
    if (exit) break;
}
//out:

求最大公约数

约数,又称因数整数a除以整数b(b≠0) 除得的正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数。

枚举法求最大公约数

int a,b;
int min;
scanf("%d %d", a,b);
if (a < b) {
    min = a;
} else {
    min = b;
}
int ret = 0;
int i;
for (i = 1; i < min; i++) {
    if (a%i == 0) {
        if (b%i == 0) {
            ret = i;
        }
    }
}
printf("%d和%d的最大公约数是%d.\n", a,b,ret);

辗转相除法求最大公约数

/**
如果b等于0,计算结束,a就是最大公约数
否则,计算a除以b的余数,让a等于b,b等于那个余数
回到第一步
a   b   t
12  18  12
18  12  6
12  6   0
6   0
*/
int a,b,t;
scanf("%d %d", &a ,&b);
a = 12; b = 11;
while (b!=0) {
    t = a%b;
    a = b;
    b = t;
    printf("a=%d, b=%d, t=%d\n", a,b ,t);
}
printf("gcd=%d\n", a);

数据类型

C语言变量使用之前必须定义变量

C语言之后的语言有两个发展方向

C++/Java更强调类型,对类型检查更加严格

JavaScript、PHP、Python不看重类型,甚至不需要事先定义

  • 整数

    char、short、int、long、long long (加粗的是c99才有的类型)

  • 浮点数

    float、double、long double

  • 逻辑

    bool

  • 指针

  • 自定义类型

不同类型的区别:

所表达的范围:char < short < int < float < double

内存中所占的大小:1字节到16字节

内存中的表达形式:二进制数(或补码)、编码。整数的是二进制数可以直接在加法器运算,浮点数是编码的格式不能直接在加法器上做运算

sizeof

是一个运算符,给出某个类型或变量在内存中所占据的字节数,它是一个静态运算符

sizeof(int)、sizeof(i)

int a;
printf("%ld\n", sizeof(a));//4
printf("%ld\n", sizeof(double));//8
printf("%ld\n", sizeof(long double));//16
a = 6;
printf("%ld", sizeof(a++));
//sizeof是在编译的时候处理的,sizeof(a++)在编译结束后会被替换成4,所以a++这个计算压根就没有执行
printf("%d", a);//6
printf("%ld", sizeof(a+1.0));//8,因为a+1.0之后就变成double类型了
printf("szieof(char)=%ld\n", sizeof(char));//1
printf("szieof(short)=%ld\n", sizeof(short));//2
printf("szieof(int)=%ld\n", sizeof(int));//4
printf("szieof(long)=%ld\n", sizeof(long));//4
printf("szieof(long long)=%ld\n", sizeof(long long));//8
//32位操作系统下的结果
//char 1,short 2, int 4, long 4, long long 8
//64位操作系统下的结果
//char 1,short 2, int 4, long 8, long long 8

int 和 long 的长度取决于编译器(或CPU),int是用来表示计算机字长的。CPU、RAM之间的总线宽度和寄存器的容量字长在32位CPU中就是32bit,在64位CPU即使64bit,一个int就是表示一个寄存器能装多少个bit的(8bit=1字节)

整数的范围

一个字节8bit

0000 0000 => 0

1111 1111 ~ 1000 0000 => -1 ~ -128

0000 0001 ~ 0111 1111 => 1 ~ 127

char c = 255;
int i = 255;
printf("c=%d, i=%d\n", c, i);
//c: -1, i:255
//c:1111 1111 => -1
//i:00000000 00000000 00000000 11111111 => 255

//unsigned
unsigned char cc = 255;
print("c=%d\n", c);//255
//如果一个字面量常数想要表达自己是unsigned,可以后面+u或U
char a = 255U;
printf("255U=%d\n", a);//还是-1

unsigned的初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位

整数越界

整数是以纯二进制方式进行计算的,所以:

1111 1111 + 1 => 1 0000 0000 => 0

0111 1111 + 1 => 1000 0000 => -128

1000 0000 - 1 => 0111 1111 => 127

char c = 127;
int i = 255;
c= c +1 ;
printf("c=%d,i=%d\n", c, i);
//c=-128,i=255

整数的输入输出

虽然有很多种整数类型,但是输入输出的时候,只有两种格式化占位符,int或long long

%d:所有小于int的 char short int 都用 %d

%ld:所有比int大的都用 %ld (long 、long long)

%u:unsigned

%lu:unsigned long long

char c = -1;
int i = -1;
//当把所有小于int的变量传进去的时候,printf会自动转换成int的长度
printf("c=%u,i=%u\n", c, i);
//c=4294967295,i=4294967295
//c的位置实际上会被打印成,11111111 11111111 11111111 11111111

8进制16进制

以0开始的数字字面量是8进制

以0x开始的数字字面量是16进制

char c = 012;
int i = 0x12;
printf("c=%d,i=%d\n", c, i);
//c=10,i=18
  • 16进制很适合表达二进制数据,因为4位二进制正好是一个16进制位

  • 8进制的一位数字正好表达3位二进制

    • 因为早期计算机的字长是12的倍数,而非8

选择整数类型

  • 为什么有那么多整数类型?

    • 为了准确表达内存,做底层程序需要
  • 没有特殊需要就用int类型

    • 现在CPU的字长普遍是32或者64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
    • 现代编译器一般会设计内存对齐,所以更短的类型其实在内存中有可能也占据一个int的大小(虽然sizeof告诉你的更小)
  • unsigned与否只是输出的不同,内部计算是一样的

浮点类型

float 字长32 范围±(1.20×10^-38 ~ 3.40×10^38),0,±inf,nan 有效数字7

double 字长64 范围±(2.2×10^-308 ~ 1.79×10^308),0,±inf,nan 有效数字15

  • ±inf:正负无穷大

  • nan:不是一个有效数字

  • 范围:从正负两极向中间的0出发,只能很接近0,但是始终有一小段距离是浮点数无法表示的。比如:0 ~ 2.2×10^-308)

float scanf:%f printf:%f,%e

double scanf:%lf printf:%f,%e

  • %e:科学计数法的形式输出

输出精度

在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做4舍5入

printf("%.3f\n", -0.0049);//-0.005  四舍五入
printf("%.30f\n", -0.0049);//-0.004899999999999999800000000000
printf("%.3f\n", -0.00049);//-0.000

-0.004899999999999999800000000000是计算机内部的真实数字,-0.0049不能在计算机内部呗精确的表达为-0.0049。

从数学上看任意两个数之间都是连续的12之间有无数个数字连接着,但是计算机无法做到这点,只能是离散的表示12之间的数字。

为了表示0.0049计算机只能找到距离它最近的数字:-0.004899999999999999800000000000,但是实际上0.0049和-0.004899999999999999800000000000还是有距离的,这就是浮点数的误差,和精度无关。double比float能够表达更多的数,所以double能够相对表达更准确的数字,但是具体的一个数字在double里可能还是有误差的。

超过范围的浮点数

printf输出inf表示超过范围的浮点数:±∞

printf输出nan表示不存在的浮点数

一个数除以0,得到的就是无穷大

printf("%f\n", 12.0/0.0);//inf
printf("%f\n", -12.0/0.0);//-inf
printf("%f\n", 0.0/0.0);//nan
printf("%f\n", 12/0);//无法通过编译,整数不能除以0,无穷大不能用整数表达,可以用浮点数表达,虽然浮点的有效范围不包括无穷大,但是它的设计里把±∞、nan作为特殊的值定义在浮点数里面了

浮点运算的精度

float a,b,c;
a = 1.345f;//默认是double,想要是float需要+f
b = 1.123f;
c = a + b;
if (c == 2.468) {
    printf("相等\n");
} else {
    printf("不相等,c = %.10f,或%f\n",c,c);
}
//不相等,c = 2.4679999352,或2.468000
//小细节:float只有7位是有效的,所以c有效的数字是2.467999

比较两个浮点数是否相等

  • 两个浮点数直接f1 == f2可能是失败的

  • 正确的方法是:fabs(f1-f2) < 1E-12;绝对值是不是小于一个很小的数,这个数只要比能表达的精度小就可以,比如float的有效数是7位,那就1E-8就ok,更保险一点是1E-12但是没有必要,1E-8就够了

不能用浮点数去做一些精细的计算,可以业务上转换成整数去做运算,或者使用BCD的数来做计算,传统的计算器都是用BCD运算

浮点数的内部表达

浮点是用编码形式表示,不是一个二进制数

|sign|  exponent(11bit) |fraction(52bit)|           
63   62      ...        52   ...        0

浮点数在计算时是由专用的硬件部件实现的(先把编码解开->再计算->计算出结果后再编码)

计算double和float所用的部件是一样的

选择浮点类型

如果没特殊需要选择double

现代CPU能直接对double做硬件运算,性能不会比float差,在64位机器上,数据存储的速度也不比float慢

字符类型

  • char是一种整数,也是一种特殊的类型:字符。原因是因为:
    • 可以用单引号表示字符的字面量:'a', '1'。'' 也是一种字符
    • printf和scanf里用%c来输入输出字符
#include <stdio.h>

int main()
{
    {
        char c;
        char d;
        c = 1;
        d = '1';
        printf("%d\n", c);
        printf("%d\n", d);
    }
    //--------如何输入'1'这个字符给char c--------------- 
    {
        char c;
        scanf("%c", &c);//输入1 
        printf("c=%d\n", c); 
        printf("c='%c'\n", c); 
    }
    {
        char c;
        //scanf("%d", &c);
        int i;
        scanf("%d", &i);//输入49 
        c = i;
        printf("c=%d\n", c); 
        printf("c='%c'\n", c); 
    }

    {
        if (49 == '1') {
            printf("OK");
        }
    }
    //测试scanf
    {
        //输入 [12 1]、[12a]、[12    1]; 
        int i;
        int c;
        //scanf("%d %c", &i, &c);
        scanf("%d%c", &i, &c);
        printf("i=%d, c=%d, c='%c'\n", i, c, c);
    }
    return 0;
} 

大小写转换

字母在ascll表中是顺序排列的

'a' - 'A' 可以得到之间的距离,a+'a'-'A' 可以把一个大写字母变成小写字母;a+'A'-'a'可以把小写变成大写字母

char c = 'A';
c++;
printf("%c\n", c);
int i = 'Z' - 'A';//得到Z和A的距离 
printf("%d\n", i);
//'a'-'A'可以得到两者之间的距离
//A+ 'a'-'A'可以把大写字母转成小写字母
//a+ 'A'-'a'可以把小写字母转成大写字母 

逃逸字符

\b:回退一格;不同的shell会对\b有不同的解释和处理

\t:到下一个表位;1.表示每行的固定位置。2.一个\t使得输出从下一个制表位开始。3.用\t才能使上下两行对齐

\n:换行;回车和换行在以前是两个动作,现在的shell会自动用两个动作完成\n

\r:回车

\":双引号

\':单引号

\\:反斜杠本身

类型自动转换

当运算符的两边出现不一致的类型时,会自动转换成较大的类型

大的意思是能表达的数的范围更大

char -> short -> int -> long -> long long

int -> float -> double

对于printf任何小于int的类型都会被转换成int;float会被转换成double。

但是scanf不会,scanf要明确的知道后面变量的大小

1.要输入short,需要%hd;2.要输入int,需要%d;3.要输入long long,需要%ld

4.如果要以整数的形式输入char就要用以下的办法

char c;
int i;
scanf("%d", &i);
c = i;

强制类型转换

int i = 32768;
short s = (short)i;
pritnf("%d %d\n", i, s);//32768 -32768
//强制类型转换不会改变原值

bool类型

include <stdbool.h>

之后就可以使用bool和true、false

但是呢 我们没有真正的布尔类型,实际上它还是整数

#include <stdio.h>
#include <stdbool.h>
int main() 
{
    bool b = 6 > 5;
    bool t = true;
    t = 2;//这样也是能过编译的,因为实际上bool也是整数
    printf("%d\n", b);//只能用整数形式输出,输出是1。没有办法看到true或者false的值
    return 0;
}

逻辑运算

! && ||

短路

a == 6 && b == 1

a == 6 && b += 1

a == 6 || b == 1

a == 6 || b += 1

逗号运算符

逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。逗号的优先级是所有运算符中最低的;逗号的组合关系是自左向右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果

int i;
i = 3+4,5+6;
printf("%d\n", i);//7
i = (3+4,5+6);
printf("%d\n", i);//11

主要是在for中使用

for (i=0, j=10; i<j; i++,j++) ........

函数

#include <stdio.h> 

int main()
{
    //sum函数的定义在调用sum函数之后 并且 没有sum函数的原型声明,
    //那么编译器就会猜它是  int sum(int, int) 
    sum(1, 10);
    //调用函数的时候sum(a,b);参数之间的逗号(,) 不是逗号运算符,而是一种标点符号。如果写成sum((a,b))那么就会先计算逗号(,)运算得到sum(b) 
    return 0;
}

//函数的定义 
void sum(int begin, int end)
{
    int i;
    int sum = 0;
    for (i=begin; i<end; i++) {
        sum += i;
    }
    printf("%d到%d的和是%d\n", begin, end, sum);
}
#include <stdio.h> 

//函数的原型声明,用来告诉编译器sum的样子 
void sum(int begin, int end);
/*
void sum(int , int );也可以不写参数,但是为了便于阅读代码,不建议这么做
函数原型声明要写清楚参数的个数和类型,如果写成这样:void sum(); 编译器会自己猜测,导致一些不可预测的事情 
所以如果没参数就写成void sum(void);有参数就写明白了 
*/
int main()
{
    //这一步会发,1.根据原型判断,这里对不对 ;2.如果数据类型和原型不一致就会类型自动转换 
    sum(1, 10);
    return 0;
}

//函数的定义 
void sum(int begin, int end)
{
    int i;
    int sum = 0;
    for (i=begin; i<end; i++) {
        sum += i;
    }
    printf("%d到%d的和是%d\n", begin, end, sum);
}

调用函数时给的值与参数类型不匹配时C语言传统上最大的漏洞

编译器总是去帮你把类型转换好,但这很可能不是你期望的

后续的语言C++/Java在这方面很严格

void cheer(int i)
{
    printf("cheer %d\n", i);
}
int main() 
{
    cheer(2.4);//warning
    cheer(2.0);//没报错
    double f = 2.4;
    cheer(f);//没报错
    return 0;
}

本地变量

#include <stdio.h>

void swap(int a, int b);

int main()
{
    //函数内部的变量就是本地变量,函数的参数也是本地变量, 
    //又叫局部变量,自动变量 (和生存期是自动的有关)
    //生存期--什么时候这个变量出现了,到什么时候它消亡了
    //作用域--在什么范围内可以访问这个变量
    //对于本地变量,生存期和作用域都是在大括号内(也叫-块) 
    int a = 5;
    int b = 6;
    swap(a, b);
    //在语句块中 定义变量 
    if (a > b) {
        int i = 10;//i是本地变量 
    }
    i++;//此处非法,离开了块其中的变量就失效了 
    
    //也可以直接使用大括号来定义 
    {
        int i = 10;
        printf("i=%d\n", i);
        printf("a=%d\n", a);//在块里面可以使用外面的变量
        int a = 0;
        printf("a=%d\n", a);//在块里面定义一个和外面同名的变量,那么会覆盖外面的值 
     } 
     printf("a=%d\n", a);//出来了之后,块内的a消亡了,现在使用的是块外的a变量 
    return 0;
 } 
 
 void swap(int x, int y)
 {
    //x,y,t是本地变量 
    int t = x;
    x = y;
    y = t;
 }

数组

#include <stdio.h>
//数组 
int main()
{
    //-----------------------定义数组------------------------------------
    {
        int grades[10];
        int i;
        //初始化数组 
        for (i=0; i < 10; i++) {
            grades[i] = 0;
        }
        for (i=0; i < 10; i++) {
            printf("%d\n",grades[i]);
        }
    }
    
    //----输入任意多数字,求平均值,并输出大于平均值的数字,输入-1后结束-----
    {
        int x;
        double sum = 0;//sum如果为整数,那么sum/cnt的结果就是整数,不是浮点数,这样不精确。 而且printf("%f")一个整数的话,输出的是0.000 
        int cnt = 0;
        int number[10]; 
        
        do {
            scanf("%d", &x);
            number[cnt] = x;
            sum += x;
            cnt ++;
         }
        while ( x != -1);
        
        if ( cnt > 0) {
            int i;
            double average = sum/cnt;
            for(i=0; i<cnt; i++){
                if (number[i] > average) {
                    printf("大于平均数的数字为:%d\n",number[i]);
                }
            }
            {
                printf("%d:%d\n", sum, cnt);
                printf("%f\n", sum/cnt);
            }
            printf("平均数为:%f\n", sum/cnt);
        }
    }
    //----------------数组大小是否可以使用变量i定义----------------------
    {
        int i = 10;
        int arr[i];
        int j;
        //初始化数组 
        for (j = 0; j<10; j++) {
            arr[j] = 0;
        }
        //打印数组 
        for (j=0; j<10; j++) {
            printf("%d\n", arr[j]);
        }
    }

    //--------计数。统计0-9之间的每个数字出现的次数。输入-1后停止------
    {
        const int number = 10;//使用常量定义数组大小 
        int x;
        int count[number];//使用常量定义数组大小 
        int i;
        //初始化数组 
        for (i=0; i<number; i++) {
            count[i] = 0;
        }
        //输入任意数字 
        scanf("%d", &x);
        while (x != -1) {
            if (x>=0 && x<=9) {
                count[x] ++;
            }
            scanf("%d", &x);
        }
        //输出统计结果 
        for (i=0; i<number; i++) {
            printf("数字%d出现了%d次\n", i, count[i]);
        }
    }
    return 0;
}

二维数组

#include <stdio.h>

/**
找出key在数组a中的位置
@param key 要找的数字
@param a 要寻找的数组 
@param length 数组a的长度
@return 如果找到,返回其在a中的位置;找不到则返回-1 
*/
int search(int key, int a[], int length);

int main()
{
    int a[3][5];//通常理解为3行5列
    
    //列数必须给 行数可以由编译器来数 
    int b[][5] = {
        {0,1,2,3,4},
        {2,3,4,5,6},
        {1},//如果省略,表示补零 
    }; 
    //打印数组b 
    printf("打印数组b:\n"); 
    int i,j;
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            printf("%d,", b[i][j]); 
            if (j == 4) {
                printf("\n");
            }
        }   
    }
    printf("---------------\n"); 
    //不写大括号也可以,二位数组在内存中的排列和一维数组一样。 
    //可以去想象,二维数组是把这些数字逐行逐次的去内存中填满 。 
    int c[][5] = {
        0,1,2,3,4,5,6,7,8,
    };
    printf("打印数组c:\n \n"); 
    for (i = 0; i < 2; i++) {
        for (j = 0; j < 5; j++) {
            printf("%d,", c[i][j]); 
            if (j == 4) {
                printf("\n");
            }
        }   
    }
    //----------数组集成初始化----------- 
    {
        //这个叫数组的集成初始化 
        int arr[] = {2,4,6,7,1,3,5,9,11,13,23,14,32};
        //集成初始化时的定位(c99)
        int a[10] = {
            [0] =2 , [2] = 3,6 ,
        }; 
        //数组初始化0
        int b[10] = {0};
        
        int i;
        for (i = 0; i < 10; i++) {
            printf("%d, ", b[i]);
        }
    }

    //------最基础的线性查找---------
    {
         int arr[] = {2,4,6,7,1,3,5,9,11,13,23,14,32};
         int x;
         int loc;
         printf("请输入一个数字:");
         scanf("%d",&x);
         //用sizeof计算数组长度 
         loc = search(x, arr, sizeof(arr)/sizeof(arr[0]));
         if (loc != -1) {
            printf("%d在第%d个位置上\n", x, loc);
         } else {
            printf("$d不存在\n", x);
         }
    }
    
    return 0;
}

//两个细节 1.return ret 单一出口原则  2.一专多能是不好的(一个变量不能承担多个功能 ) 
int search(int key, int a[], int length) {
    int ret = -1;
    int i;
    for (i = 0; i < length; i++) {
        if (a[i] == key) {
            ret = 1;
            break;
        }
    }
    return ret;
    //这里的变量i既计数又作为判断结果的标志 
//  if ( i == length)
//  {
//      return 1;
//   }
//   else
//   {
//      return i;
//    } 
}

练习:线性搜索

结构类型

#include <stdio.h>

//提到的知识点:散列表 hash table 
int search(int key, int a[], int length);

//定义两个数组,但是这两个数据以后写代码遇到cache不友好,因为它是分开的两个数组 
int amount[] = {1,5,10,25,50};
char *name[] = {"penny", "nickel", "dime", "quarte", "half-dollar"};
//结构这个东西对cache友好(相当于php的关联数组) 
struct {
    int amount;
    char *name;
} coins[] = {
    {1,"penny"},
    {5,"nickel"},
    {10,"dime"},
    {25,"quarte"},
    {50,"half-dollar"},
}; 
int main ()
{
    int k = 10;
    //以两个数组的方法搜索 
    int r = search(k, amount, sizeof(amount)/sizeof(amount[0]));
    if (r > -1)
    {
        printf("%s\n", name[r]);
    } 
    //以结构的方法搜索 
    int i; 
    for (i=0; i < sizeof(coins)/sizeof(coins[0]); i ++)
    {
        if (k == coins[i].amount) {
            printf("%s\n", coins[i].name);
            break;
        }
     } 
    
    return 0;
} 
 
int search(int key, int a[], int length) {
    int ret = -1;
    int i;
    for (i = 0; i < length; i++) {
        if (a[i] == key) {
            ret = 1;
            break;
        }
    }
    return ret; 
}

练习:二分法搜索

#include <stdio.h>
//二分法,每次砍一半的比较,
//第一步:用中间值mid=(left+right)/2;和x比较,x小于中间值说明x在:(1).【left ~ mid】之间;反之在(2).【mid ~ right】之间
// 第二步:如果(1)砍掉右边的一半:right=mid-1;如果(2)砍掉左边的一半:left=mid+1
// 重复 第一步和第二部直到mid的值等于x 
int serach(int k, int a[], int len);
int main ()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10,11,12,13};
    int len = sizeof(a)/sizeof(a[0]);
    int x = 0;
    int res = 0;
    printf("请输入一个数字:\n");
    scanf("%d", &x);
    res = search(x,a,len);
    if (res > -1) {
        printf("%d\n", res);
    }
    
    return 0;
 } 
 
 //左下标left,右下标right,中间下标mid 
 int search(int k, int a[], int len)
 {
    int left = 0;
    int right = len-1;
    int ret = -1;
    while(left < right){
        int mid = (left+right)/2;
        
        if (k < a[mid]) {
            right = mid-1;
        } else if (k > a[mid]) {
            left = mid+1;
        } else {
            ret = mid;
            break;
        }
    }
    return ret;
 }

练习:选择排序

#include <stdio.h>

int max(int a[], int len); 

int main()
{
    int a[] = {123,345,6,756,9,807,3,3,5,34,12,321,25,34,467,547,57,34,7,9,89,0,0,3,2,1,123,123,34,34,5,71};
    int maxId = 0;
    int len = sizeof(a)/sizeof(a[0]);
    int swap = 0;
    int i;
    //选择排序,每次选出一个最大的放到数组的最后面 
    for (i = len-1; i > 0; i--) 
    {
        maxId = max(a, i+1);
        //交换值 
        swap = a[i];
        a[i] = a[maxId];
        a[maxId] = swap;
    }
    //打印出排好序的数组 
    for (i = 0; i < len; i++)
    {
        printf("%d ", a[i]);
    }
    
    return 0;
 } 
 
 int max(int a[], int len)
 {
    int i; 
    int maxId = 0;
    for (i=0; i<len; i++)
    {
        if(a[maxId] < a[i])
        {
            maxId = i;
        }
    }
    return maxId;
 } 
 

取址运算符

取址运算符& ,它的操作数必须是变量 ,(实际测试得到结果:也可以是const定义的常量,也可以是字符串字面量 )

#include <stdio.h>

int main()
{
    //地址的大小是否与int相同取决于编译器 
    //想要printf输出地址需要使用%p,而不是真的把地址当成整数,地址和int并不是永远相同 
    int i = 0;
    
    printf("0x%x\n", &i);
    printf("%p\n", &i);

    int p;
    p = (int)&i;//32位架构下这么做会导致p的值不是i的地址 
    printf("0x%x\n", p);
    printf("%p\n", p);

    //32为架构下下面的打印值就是4,4
    //64位架构下是4,8 
    printf("%d\n", sizeof(int)); // %lu32位无符号整数
    printf("%d\n", sizeof(&i)); 

    //相邻两个变量在内存中的地址关系
    //ab的地址正好相差一个int大小
    //变量在内存中放在堆栈中 ,在堆栈中我们分配内存地址是由大到小分配 
    int a = 0;
    int b = 0;
    printf("%p\n", &a); 
    printf("%p\n", &b); 

    //数组的地址
    int arr[10];
    printf("数组的地址\n"); 
    printf("%p\n", arr); 
    printf("%p\n", &arr); 
    printf("%p\n", &arr[0]); 
    printf("%p\n", &arr[1]); 
    return 0; 
}

指针

#include <stdio.h>

void f(int *p);
void g(int k); 

int main()
{
    //指针变量,就是保存地址的变量
    /** 
    int i;
    int* p = &i;
    int* p,q;//p是指针类型变量,q是int类型变量 
    int *p,q;//p是指针类型变量,q是int类型变量 
    */
    int i = 6;
    printf("&i=%p\n", &i);
    f(&i);//传值给指针类型参数 
    g(i);
    return 0;
} 

void f(int *p)
{
    printf(" p=%p\n", p);
    /** 
        //*是一个运算符,用来访问指针的值所表示的地址上的变量
        //可以做右值可以做左值
        int k = *p;
        *p = k + 1; 
    */
    printf("*p=%p\n", *p);//使用*运算符访问这个地址上的变量 
    //*p = 26;//在函数里面可以修改i的值 
}

void g(int k)
{
    printf("k=%d\n", k);
}

数组与指针

#include <stdio.h>

void minmax(int a[], int len, int *min, int *max);

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,};
    int min,max;
    printf("main sizeof(a)=%lu\n", sizeof(a));
    printf("main a =%p\n", a);
    minmax(a, sizeof(a)/sizeof(a[0]), &min, &max);
    printf("a[0]=%d\n", a[0]);
    printf("min=%d,max=%d\n", min, max);
    //数组变量是特殊的指针 
    //  int a[10]; int *p=a;不需要用&就能取地址
    //  但是数组的单元表达的是变量 ,需要用&取地址
    //  a == &a[1]; 
    
    //[]运算符可以对数组做,也可以对指针做
    //  p[0] <==> a[0] 
    int *p = &min;
    printf("*p=%d\n", *p);
    printf("p[0]=%d\n", p[0]);//p[0],指的是我以为p所指的地方是一个数组那么我取第一个元素 
    //*运算符也可以对数组做
    //  *a = 25; 
    printf("*a=%d", *a);
    //实际上数组变量是const的指针,所以不能被赋值 
    int b[] = {0};//可以被看作int * const b;这个b是一个常量他不能被改变 
    //b = a; //所以这样做是不允许的 
    return 0;
}

//函数参数表中的数组实际上是指针
//以下四种原型是等价的
//int sum(int *ar, int n);
//int sum(int *, int);
//int sum(int ar[], int n);
//int sum(int [], int); 
// int *ar和int ar[]只是在函数参数时是等价的
void minmax(int a[], int len, int *min, int *max)
{
    int i;
    printf("minmax sizeof(a)=%lu\n", sizeof(a));
    printf("minmax a=%p\n", a);
    //a[0] =  100;//在function内改变外部的数组值 
    *min = *max = a[0];
    for (i=1; i<len; i++)
    {
        if (a[i] < *min) {
            *min = a[i];//函数内改变外部变量值 
        }
        if (a[i] > *max)
        {
            *max = a[i];
        }
    }
}

字符串

# include <stdio.h>

int main()
{
    //这是字符数组,不是字符串,因为不能用字符串的方式做计算 
    char word[] = {'H', 'e', 'l', 'l', 'o', '!'};
    //这是字符串
    char word1[] = {'H', 'e', 'l', 'l', 'o', '!', '\0'};
    //'\0'代表的就是整数0 
    //word1还是一个字符数组,但是因为他的后面有一个0,他就成为了c语言的中可以使用字符串运算规则运算的‘字符串’

    //'\0'和0一样,某些时候强调使用'\0',因为它是一个字节,而0是int会是四个字节 
    char word2[] =  {'H', 'e', 'l', 'l', 'o', '!', 0};
    //0标志着字符串的结束,但是他不是字符串的一部分,计算字符串的长度时不包含这个0
    //字符串在内存中以数组的形式存在(表达形式是数组--一片连续的空间),
    //可以用数组或者指针的形式访问,更多的是用指针的形式访问 ,但是字符串它是数组

    //定义字符串变量 
    char *str = "Hello";
    char word3[] = "Hello";
    char line[10] = "Hello"; 

    /**
    "Hello"是一个字符串字面量,或者叫字符串常量,
    它会被编译器变成一个字符串数组放在某处。
    这个数组长度是6,结尾还有表示结束的0
     */

    //如果有两个相邻的字符串,c语言会自动帮忙连接起来两个字符串成一个大字符串 
    printf("请分别输入身高的英尺和英寸"
           "如输入\"5 7\"表示5英尺7英寸\n"); 
    //反斜杠表示这一行的字符串还没有结束,下一行的内容也是这个字符串的部分 
    //这样也可以连接两个字符串,但是下一行开始的tab也会被打印出来
    printf("请分别输入身高的英尺和英寸\
            如输入\"5 7\"表示5英尺7英寸\n"); 

    //c语言的字符串是以字符数组的形态存在的
    //所以不能用运算符对字符串做运算(+-*/) java、php等就可以用+连接字符串 
    //c诞生的年代计算机主要处理数字计算,所以c对字符串的支持有限, 
    //唯一特殊的地方是字符串字面量可以用来初始化字符数组(这是唯一c语言表现出来它懂字符串的地方)

    return 0;
}

字符串变量

#include <stdio.h>

int main()
{
    char *s = "Hello World";
    //s[0] = 'B';//此处出错,编译和运行没有报错,但是后面的代码完全没执行 
    
    printf("Here!s[0]=%c\n", s[0]);
    
    char *s2 = "Hello World";
    printf("s =%p\n", s);
    printf("s2=%p\n", s2);
    //s1和s2的地址一样,用同样的字符串字面量初始化了两个字符串变量,这两个变量指向了同一个地址 
    int i = 0;
    printf("&i=%p\n", &i); 
    //i是本地变量,通过打印地址发现,i的地址和s1 s2所指的地址不在一个地方 ,他们离得很远
    //"Hello World"所在的地址号很小,这个地方叫 《代码段》,代码段是只读的
    //如果试图对代码段进行写的操作,那么操作系统会有一种保护机制,会认为你在做坏事情,让这个程序崩溃掉。
    //如果让你写进去了,那就说明这个操作系统不够好没有起到保护作用
    
    /**
        "Hello world"是在编译的时候就有值的,编译器把它放在一个只能读不能写的地方,
        其它变量也要被赋值成这个字符串常量的值时,就会直接使用"Hello world"那个地方的地址 
        所以s1和s2地址是一样的。c语言是早期的语言没有做到字符串处理自动化的功能 
    */ 
    
    /**
        char *s = "Hello World";
        s是一个指针,初始化为指向一个字符串常量,由于这是一个常量所在的地方,所以实际上s是const char *s,
        但是由于历史的原因,编译器接受不带const的写法。
        但是试图对s所指的字符串做写入会导致严重的后果。 
    */ 
    //如果需要修改字符串,应该用数组
    char ss[] = "Hello, world!";
    //字符数组声明是把不可写的字符串常量复制到可写的本地变量来了 
    //数组:这个字符串在这里 ;作为本地变量空间自动被回收
    //指针:这个字符串不知道在哪里
    //什么时候用指针:1.单纯的就是表达一个字符串常量 
    //                2.表示函数的参数(当参数时,数组形式其实就是指针形式,不妨就用指针来表示) 
    //                3. 使用动态分配空间时
    //结论:如果要构造一个字符串,就用数组;如果要处理一个字符串,就用指针
    
    //char*是字符串?
    //字符串可以表达为char*的形式,但是char*不一定就是字符串 就好像int*不一定代表是整数数组一样
    //只有当char*所指的字符数组有结尾的0,才能说它所指的就是字符串 
    return 0;
}

字符串的输入输出

%s

#include <stdio.h>

int main()
{
    //c语言处理字符串的能力还是不足的
    char *t = "title";
    char *s;
    s = t;
    //并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s做的任何操作就是对t做的,因为他俩指向同一个内存地址
    
    //输入hello world 
    char string[8];
    char string2[8];
    scanf("%s",string); //读字符不读空格回车tab,所以scanf读入一个单词(到空格、tab或回车为止) 
    scanf("%s", string2); 
    printf("%s##%s##\n",string, string2); 
    //安全的输入,试试输入12345678 
    scanf("%7s", string);//%和s之间的数字应该比数组的大小小一 
    scanf("%7s", string2);
    printf("%s##%s##\n",string, string2); 
    /**常见错误
    char *string;
    scanf("%s",string);
    误以为char*就是字符串类型; 
    没有初始化string为0 
    */
    /**
    char buffer[100]="";
    这是一个空字符串,buffer[0]=='\0'
    char buffer[]="";
    这个数组长度只有1 ,buffer[0]=='\0',这个buffer里放不下任何字符串 
    */ 
     
    return 0;
}

常用的字符串函数

//size_t strlen(const char *s);
//int strcmp(const char *s1. const char *s2)

//char * strcpy(char *restrict dst, const char *restrict src)
//char * strcat(char *restrict s1, const char *restrict s2)
//strcpy strcat 有安全问题,因为目的str没有足够的空间就会出错 
//安全的版本
//char * strncpy(char *restrict dst, const char *restrict src, size_t n)
//char * strncat(char *restrict s1, const char *restrict s2, size_t n)

//int strncmp(const char *s1, const char *s2, size_t n);n代表比较前几个字符 
//char * strchr(const char *s, int c);从做左开始找 
//char * strchr(const char *s, int c);从右开始找 

const指针\指针运算

#include <stdio.h>

int main()
{
    int i= 0;
    int j = 0;
    //指针是const,一旦得到了某个变量的地址,不能再指向其它变量
    
    int * const q = &i;//q是const 
    *q = 26;//ok
    //q++;//error
    //q = &j;//error
    
    //所指的是const,表示不能通过这个指针取修改那个变量(并不能是得那个变量成为const)
    
    const int *p = &i;
    //*p = 26;//error
    i = 26;//ok
    p = &j;//ok
    
    //const数组
    const int a[] = {1,2,3,4,5,};
    //数组变量已经是const指针了,这里的const表明数组得每个单元都是const int,所以必须通过初始化进行赋值 
    return 0;
}

指针运算

//sizeof(char) = 1; sizeof(int) = 4
char ac[] = {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[] = {1,2,3,4,5,6,7,8,9,};
int *q = ai;
printf("q  =%p\n", q);
printf("q+1=%p\n", q+1);
//指针+1是在加上它类型的sizeof个单位,就是向右移动一个单元 
printf("*(p+1)=%d\n", *(p+1));
//*p     -> ac[0]
//*(p+1) -> ac[1]
//*(p+n) -> ac[n]

char *p1 = &ac[5];
int *q1 = &ai[6];
printf("p    =%p\n", p);
printf("p1   =%p\n", p1);
printf("p1-p =%d\n", p1-p);

printf("q    =%p\n", q);
printf("q1   =%p\n", q1);
printf("q1-q =%d\n", q1-q);
//两个指针相减得到的不是两个地址之间相减的值,而是两个指针之间相隔了几个单元 

*p++;//取出p所指得那个数据来,完事之后顺便把p移到下一个位置去 
//*的优先级虽然高,但是没有++高
//常用于数组类的连续空间操作
//某些CPU上。这个可以直接被翻译成一条汇编指令 

//<,<=,==,>,>=,!=都可以对指针做(但是不能做*/法),比较它们在内存中的地址 

0地址

#include <stdio.h> 

int main()
{
    /**
    我们现代的操作系统 ,都是多进程的操作系统,这些系统的基本管理单元是进程,
    什么是进程:比如你双击一个东西,它运行起来了,它就是操作系统里的一个进程 ,
    对于进程来说它最基本的概念,操作系统会给他一个虚拟的地址空间,也就是说所有程序运行时都以为它具有从0到X的一片连续的内存空间 ,
    X是多少呢,如果32位操作系统 就是4G 
    所以任何程序都有0地址,这个是个虚拟的0地址 ,但是这个虚拟的物理上怎么翻译是另外一回事,我们有一门课叫操作系统 ,在那门课里面我们会详细讲 

    当然你的内存中可以有0地址,但是0地址通常是个不能随便碰的地址
    所以你的指针不应该具有0值
    因此可以用0地址来表示特殊的事请:
        1.返回的指针是无效的
        2.指针没有被真正的初始化(先初始化为0)
        
    NULL是一个预定义的符号,表示0地址(NULL一定要是大写) 
    建议使用NULL不用0,有的编译器不愿意你用0来表示0地址 ,有的编译器0和NULL不相等 
    */
    
    /**
    无论指针指向什么类型(无论什么类型的指针),所有指针的大小都是一样的,因为都是地址。 
    指向不同类型的指针是不能直接互相赋值的,这是为了避免用错指针 
    */
    int ai[] = {1,2,4};
    int *q = ai;
    char *p = NULL;
    //p = q; //warning 
    
    /**
    指针类型的转换
    void* 表示不知道指向什么类型的指针,这个往往会用在底层系统程序上,我要直接去访问某个内存地址,内存地址所代表的比如外部设备、控制寄存器等 
        计算时与char*相同(但不相通)
    指针也可以转换类型
        int *p = &i;void *q = (void*)p; 
    这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
    我不再当你是int了,我认为你就是个void 
    */
    
    /**
    总结:用指针来做什么
    需要传入较大的数据时用作参数
    传入数组以后对数组做操作 
    函数返回不止一个结果 ,用指针做参数,让它带出结果 
      用函数来修改不止一个变量 
    动态申请内存的时候 
    */ 
    return 0;
}

动态内存分配

#include<stdio.h> 
#include<stdlib.h>
int main()
{
    /**
    如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
    c99可以用变量做数组定义的大小,c99之前呢? 
    */ 

    int number;
    int *a;
    int i;
    //c99之前变量都要写在头上
     
    printf("输入数量:");
    scanf("%d", &number);
    //int a[number];//c99
    //void* malloc(size_t size);向malloc申请的空间的大小是以字节为单位的,返回的类型为void* 
     a = (int*)malloc(number*sizeof(int));
     for(i = 0; i < number; i++){
        scanf("%d", &a[i]);
     }
     for (i=number-1; i>=0; i--) {
         printf("%d", a[i]);
     }
     //对a的操作,完全把它当作数组来操作
     //试验了定义a为数组之后分配动态内存给a报错,所以只能用指针 
     
     free(a);//用完了要释放内存 
     
     /**
     malloc向系统要空间,如果没有空间了,申请失败返回0,或者叫做NULL 
     */
     void * p = 0;//写指针的好习惯,定义指针时就初始化为0 
     int cnt = 0;
     while( (p=malloc(1024*1024*100)) ){
        cnt++;
     }
     printf("%d00M\n", cnt);

    free(p);//不会报错 
    //0不可能是一个有效的地址,0不可能是malloc得到的地址,free()里会判断如果传入的是NULL那就不做处理 
    //这里free(p),可以配合上面的int *p = 0;这个好习惯(当中间没有malloc的时候不会报错) ,因为free(0)也不会报错; 
    
    /**
    常见问题
    申请了没free->长时间运行内存逐渐下降
        新手:忘了
        老手:找不到合适的时机free
    free过了再次free就报错
    地址变过了(p++),直接去free 
    
    解决办法:
    牢牢记住有malloc就有free
    对程序的整体架构有良好的设计,保证有合适的时机去free
    需要经验,需要多阅读别人的代码,需要多写自己的代码,多总结经验 
    */ 
    return 0;
}  

单字符输入输出

#include <stdio.h> 

int main()
{
    //这集讲了关于shell缓冲区的知识,不好总结,详情请看82集 
    
    //int putchar(int c);参数奇怪是int, 但是实际上它接收的是一个字符(char),你给的一个char它当成int接收了 
    //向标准输出写一个字符
    //返回写了几个字符,EOF(-1)表写失败 
    //EOF(end of file)时C语言里面定义的一个宏,每个宏都会有一个值,EOF的值是-1 
    
    //int getchar(void);
    //从标准输入读入一个字符 
    //返回类型是int 是为了返回EOF(-1) 来表示标准输入结束了 
    //Windows->Ctrl-Z 
    //Unix->Ctrl-D
    
    int ch;
    while((ch = getchar()) != EOF){
        putchar(ch);
    }
    printf("EOF\n");
    return 0;
}

字符串数组

#include <stdio.h>

int main(int argc, char const *argv[])
{
    /**
    char **a;
        a是一个指针,指向另一个指针,那个指针指向一个字符(串) ,所以它不是我们要的字符串数组 
    */ 
    /**
    char a[][10]; a是一个二维数组 
    */
    char a[][10] = {
        "Hello",
        "World",
        "sdasASsSaADASDASDasdds",//超出10 这样会warning 
    };
    
    //这样是可以的 
    //b[0] --> char*
    char *b[] = {
        "Hello",
        "World",
        "addsadadasddadadadasda",
    };
//  *a[0] = "www";
    printf("%p\n", a[1]);
    //程序参数
    
    /**
    int main(int argc, char const *argv[])
    argv[0]是命令本身,当使用Unix的符号链接时,反应符号链接的名字   
    */ 
    
    int i;
    for (i=0; i<argc; i++) {
        printf("%d:%s", i, argv[i]);
    }
    return 0;
}

字符串函数的实现

strlen、strcmp、strcpy

#include <stdio.h>
#include <string.h>
#include<stdlib.h>
int mylen(const char *s)
{
    int len = 0;
    while(s[len] != '\0'){
        len++;
    }
    return len;
}

int mycmp(const char *s1, const char *s2)
{
    int idx = 0;
    //第一种实现方式 
    while(1){
        if (s1[idx] != s2[idx])
        {
            break;
        }
        else if (s1[idx] == '\0' || s2[idx] == '\0')
        {
            break;
        }
        idx++;
    }
    //第二种实现方式 
    while((s1[idx] != '\0' && s2[idx] != '\0') && s1[idx] == s2[idx]){
        idx++;
    }
    //第三种实现方式
    while(*s1 == *s2 && (*s1 != '\0' && *s2 != '\0'))
    {
        *s1++;
        *s2++;
    }
    //return *s1-*s2;
    return s1[idx] - s2[idx];
}
/**
我们自己写的就先不用和标准库那样高端,不处理dst和src的重叠 
*/
char * mycpy(char * dst, const char *src)
{
    //版本1 数组形式 
//  int idx = 0;
//  while(src[idx] != '\0'){
//      dst[idx] = src[idx];
//      idx++;
//  }
//  dst[idx] = '\0';
    
    //指针形式 
    char *res = dst;
    //版本2 
    while(*src != '\0'){
        *dst++ = *src++;
        dst++;
        src++;
    }
    //版本3 
//  while(*src != '\0'){
//      *dst++ = *src++;
//  }
    //版本4 
//  while(*src){
//      *dst++ = *src++;
//  }
    //版本5 
//  while(*dst++ = *src++)
//      ;
    //以上版本怎么写都行,可读性好就行,因为编译器最后基本都会优化成一个样子所以效率问题就不存在了 
    *dst = '\0';
    
    
    return res;
}
int main()
{
    //  size_t = strlen(const char *s) 字符串长度 
    char line[] = "Hello";
    printf("strlen=%lu\n", strlen(line));
    printf("szieof=%lu\n", sizeof(line));
    printf("mylen=%lu\n", mylen(line));
    
    //  int strcmp(const char *s1, const char *s2) 比较字符串大小 
    char s1[] = "abc";
    char s2[] = "Abc";  
    int res;
    res = strcmp(s1, s2);
    printf("res = %d\n", res);
    printf("strcmp = %d\n", strcmp(s1, s2));
    if(res == 0)
    {
        printf("s1==s2\n" );
    }
    else if(res == -1)
    {
        printf("s1<s2\n");
    }
    else
    {
        printf("s1>s2\n");
    } 
    
    printf("a-A=%d\n", 'a'-'A');
    printf("mycmp=%d\n", mycmp(s1, s2));
    //详情请看视频--函数strcpy 
    //  char * strcpy(char *restrict dst, const char *restrict src)把src字符串拷贝到dst。restrict表明src和dst不重叠(C99)
    //有返回 返回的是dst,这么做是为了能链接起来代码 
    //复制一个字符串
    char src[] = "hhhh"; 
    char *dst = (char*)malloc(strlen(src)+1);
    strcpy(dst, src); 
    return 0;
}

strcat、strchr、strstr

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    /**
    char * strchr(const char *s, int c);
    char * strrchr(const char *s, int c);//从右边找 
    返回NULL表示没找到 
    */
    
    char s[] = "hello world";
    char *p = strchr(s, 'l');
    /**
    p = strchr(p+1, 'l');//找第二个l 
    printf("%s\n", t);
    */
    /**
    复制l和后面的字符串到一个变量里 
    char *t = (char*)malloc(strlen(p)+1);
    strcpy(t, p);
    printf("%s\n", t);
    free(t);
    */
    
    //取要找的字符前面的字符串 
    char c = *p;
    *p = '\0';
    char *t = (char*)malloc(strlen(s)+1);
    strcpy(t, s);
    printf("%s\n", t);
    free(t);
    *p = c;//恢复 
    
    /**
    char * strstr(const char *s1, const char *s2);//在一个字符串中,找一个字符串 
    char * strcasestr(const char *s1, const char *s2);//忽略大小写 
    */ 
    return 0;
}

枚举

#include <stdio.h>
/**
枚举是一种用户定义的数据类型,他用关键字enum
enum 枚举类型的名字{名字0, 名字1..., 名字n}; 
*/
//RED是0,YELLOW是1,GREEN是2 ,NunCOLORS是3-->小套路,自动计数。这样需要遍历所有的枚举量或者需要建立一个用枚举做下标的数组的时候就很方便了  
enum COLOR {RED, YELLOW, GREEN, NumCOLORS};//这一行是在声明一种新的数据类型,这个新的类型就叫COLOR ,可以像int,float这样的类型去用,但是用的时候需要加上enum ,enum COLOR 
/**
枚举类型的名字通常不真的使用,要用的是大括号里的名字,因为他们就是常量符号,他们的类型是int(内部就是把它当作int来处理的),值依次是从0到n 

当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字 
*/

void f(enum COLOR c); 

int main( int argc, char const *argv[])
{
    int color = -1;
//  char *ColorNames[NumCOLORS] = {
//      "red", "yellow", "green",
//  };
    char *colorName = NULL;
    printf("输入你喜欢的颜色的代码:");
    scanf("%d", &color);
    switch(color){
        case RED: colorName = "red";break;
        case YELLOW : colorName = "yellow";break;
        case GREEN: colorName = "green";break;
        default : colorName = "unkonwn";break;
    }
    
//  if (color >=0 && color < NumCOLORS) {
//      colorName = ColorNames[color]; 
//  } else {
//      colorName = "unknown";
//  }
    
    printf("你喜欢的颜色是%s\n", colorName);
    
    //-------------------------------
    {
        enum COLOR t = RED;
        //scanf("%d", &t);
        f(t); 
    }
    return 0;
}

void f(enum COLOR c)
{
    printf("%d\n", c);
}
/**
枚举量

enum COLOR{RED=1,YELLOW,GREEN=5};//声明枚举量的时候可以指定值,YELLOW是2 
ennum COLOR color = 0;//现在的编译器即使给枚举类型的变量赋不存在的整数值也没有任何warning和error ,历史上有的编译器会强迫你,给枚举类型的变量赋值的时候必须使用枚举量 

虽然枚举类型可以当作类型使用,但实际上很(bu)少(hao)用;
如果有意义上排比的名字,用枚举比const int 方便 
枚举比宏好,因为枚举有int类型 
枚举在c语言中不是那么成功 
*/

结构

#include <stdio.h>

//和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
//所以通常在函数外部声明结构类型,这样就可以被多个函数使用了 
struct date {
    int month;
    int day;
    int year;
}; 
/**
声明结构的形式 
1.
struct point{
    int x;
    int y;
}; 
struct point p1,p2;//p1,p2都是point,里面有x和y的值 
2.
struct {
    int x;
    int y;
} p1,p2;     //p1和p2都是一种无名结构,里面有x和y 
3.
struct point {
    int x;
    int y;
} p1,p2;   /p1和p2都是point,里面有x和y 

声明结构其实就是在声明一种新的变量类型 
*/ 
int main(int arge, char const *argv[])
{
    struct date today;//定义这种结构类型的变量 
    today.month = 07;
    today.day = 31;
    today.year = 2014;
    //结构的初始化
    struct date tomoro = {07,31,2014};
    struct date thismonth = {.month=7, .year=2014}; //其中day的值会是默认的0 
    printf("Today`s date is %i-%i-%i.\n",
        today.year,today.month,today.day
    );
    printf("Tomoro`s date is %i-%i-%i.\n",
        tomoro.year,tomoro.month,tomoro.day
    );
    printf("Thismonth`s date is %i-%i-%i.\n",
        thismonth.year,thismonth.month,thismonth.day
    );
    /**
    结构的成员和数组的单元有点像,不同的是结构的成员可以是不同类型的,数组的单元都是同一类型的
    数组 
    a[0] = 10;
    结构类型 
    today.day
    student.firstName 
    */
    /**
    结构运算
    要访问整个结构,直接用结构变量的名字 ;
    对于整个结构,可以做赋值、取地址,也可以传弟给函数参数
    p1 = (struct point){5,10};//相当于p1.x=5; p1.y=10;
    p1 = p2; // 相当于p1.x=p2.x; p1.y=p2.y 
    */
    struct date yxp;
    yxp = (struct date){07,31,2014};
    struct date yyy;
    yyy = yxp;//结构变量之间可以赋值, 但是yxp和yyy两个变量 有各自的空间 
    printf("yxp`s date is %i-%i-%i.\n",
        yxp.year,yxp.month,yxp.day
    );
    printf("yyy`s date is %i-%i-%i.\n",
        yyy.year,yyy.month,yyy.day
    );
    yyy.year = 2021;
    printf("yyy`s date is %i-%i-%i.\n",
        yyy.year,yyy.month,yyy.day
    );
    //和数组不同,结构变量的名字并不是结构变量的地址,必须用&运算符 
    struct date *pDate = &today; 
    printf("%p\n", pDate);
    
    return 0;
} 

结构与函数

整个结构可以作为参数的值传入函数

这个时候是在函数内新建一个结构变量,并复制调用者的结构值

也可以返回一个结构

#include <stdio.h>

struct date {
    int month;
    int day;
    int year;
};

bool isLeap(struct date d);
int numberOfDays(struct date d); 
int main(int argc, char const *argv[])
{
    struct date today, tomorrow;
    
    printf("Enter today`s date (mm dd yy):");
    scanf("%i %i %i", &today.month, &today.day, &today.year);
    
    if(today.day != numberOfDays(today)){
        tomorrow.day = today.day+1;
        tomorrow.month = today.month;
        tomorrow.year = today.year;
    } else if (today.month == 12) {
        tomorrow.day = 1;
        tomorrow.month = 1;
        tomorrow.year = today.year+1;
    } else {
        tomorrow.day = 1;
        tomorrow.month = today.month+1;
        tomorrow.year = today.year;
    }
    
    printf("Tomorrow`s date is %i-%i-%i.\n", tomorrow.year, tomorrow.month, tomorrow.day);
    return 0;
}

int numberOfDays(struct date d)
{
    int days;
    
    const int daysPerMonth[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
    if (d.month == 2 && isLeap(d))
        days = 29;
    else
        days = daysPerMonth[d.month-1];
    return days;
}

bool isLeap(struct date d)
{
    bool leap = false;
    if ( (d.year %4 == 0 && d.year % 100 != 0) || d.year%400 == 0 )
        leap = true;
    return leap;
}

输入结构

没有直接的方式可以一次scanf一个结构

可以自己写一个函数来scanf

struct point {
  int x;
  int y;
};
void getStruct(struct point);
void output(struct point);
void main()
{
    struct point y = {0,0};
    getStruct(y);
    output(y);
}
//结构和数组不一样,参数是传值的方式,传入函数的是外面结构的克隆体,不是指针
void getStruct(struct point p)
{
    scanf("%d",&p.x);
    scanf("%d",&p.y);
    printf("%d,%d\n",p.x, p.y);
}
void output(struct point p)
{
    printf("%d,%d",p.x,p.y);
}

如何解决以上,函数里面能把对结构的影响传出呢?

方案1.在这个函数中创建一个临时的结构变量,然后把这个结构返回给调用者

struct point {
  int x;
  int y;
};
struct point getStruct(void);//*
void output(struct point);
void main()
{
    struct point y = {0,0};
    y = getStruct();//*
    output(y);
}
struct point getStruct(void)//*
{
    struct point p;//*
    
    scanf("%d",&p.x);
    scanf("%d",&p.y);
    printf("%d,%d\n",p.x, p.y);
    return p;
}
void output(struct point p)
{
    printf("%d,%d",p.x,p.y);
}

指向结构的指针

比较推荐这种结构指针作为参数的方式,因为传值给函数,会在函数内新建一个一摸一样的结构,这样费时费空间。

(*p).month = 12;
p->month = 12;//简略的写法

struct date{
    int month;
    int day;
    int year;
} myday;
//结构变量本身不是地址,所以要用&取址
struct date *p = &myday;
//指向结构的指针
(*p).month = 12;
p->month = 12;//简略的写法

结构指针作为参数

struct point {
  int x;
  int y;
};
struct point* getStruct(struct point*);//*
void output(struct point);
void print(const struct point *p);//*
void main()
{
    struct point y = {0,0};
    getStruct(&y);//*
    output(y);
    output(*getStruct(&y));
    print(getStruct(&y));
}
struct point* getStruct(struct point *p)//*
{
    scanf("%d",&p->x);
    scanf("%d",&p->y);
    printf("%d,%d\n",p->x, p->y);
    return p;
}
void output(struct point p)
{
    printf("%d,%d",p.x,p.y);
}
void print(const struct point *p)
{
    printf("%d,%d",p->x,p->y);
}

结构数组

struct time{
    int hour;
    int minutes;
    int seconds;
};
struct time timeUpdate(struct time now);

int main(void)
{
    struct time testTimes[5] = {
        {11,59,59},{12,0,0},{1,29,59},{23,59,59},{19,12,27}
    };
    int i;
    for(i=0; i<5; ++i){
        printf("Times is %.2i:%.2i:%.2i",
              testTimes[i].hour, testTimes[i].minutes, testTimes[i].seconds);
        testTimes[i] = timeUpdate(testTimes[i]);
        printf("...one seconds later itis %.2i:%.2i:%.2i\n",
              testTimes[i].hour, testTimes[i].minutes, testTimes[i].seconds);
    }
    return 0;
}
struct time timeUpdate(struct time now)
{
    ++now.seconds;
    if(now.seconds == 60){
        now.seconds = 0;
        ++now.minutes;
        
        if(now.minutes == 60){
            now.minutes = 0;
            ++now.hour;
            if(now.hour == 24){
                now.hour = 0;
            }
        }
    }
}

结构中的结构

struct dateAndTime{

struct date sdate;

struct time stime;

};

struct point{
    int x;
    int y;
};
struct rectangle{
    struct point pt1;
    struct point pt2;
};
//如果有struct rectangle r;
//就可以有
//r.pt1.x
//r.pt1.y
//r.pt2.x
//r.pt2.y
//如果
struct rectangle r,*rp;
rp = &r;
//那么以下四种形式是等价的
//r.pt1.x
//rp->pt1.x
//(r.pt1).x
//(rp->pt1).x
//但是没有rp->pt1->x

结构中的结构的数组

#include <stdio.h>
struct point{
    int x;
    int y;
};
struct rectangle {
    struct point p1;
    struct point p2;
};
void printRect(struct rectangle r)
{
    printf("<%d, %d> to <%d, %d>\n", r.p1.x, r.p1.y, r.p2.x, r.p2.y);
}
int main()
{
    int i;
    struct rectangle rects[] = {
        {{1, 2}, {3, 4}},
        {{5, 6}, {7, 8}}
    };
    for(i = 0; i<2;  i++){
        printRect(rects[i]);
    }
}

自定义数据类型:typedef

C语言提供了一个叫做typedef的功能来声明一个已有的数据类型的新名字,typedef改善了程序的可读性,比如:

//Length成为int的别名
typedef int Length;
Length a,b,len;
Length numbers[10];

typedef long int64_t;
int64_t i = 100000000000000;

typedef struct ADate {
    int month;
    int day;
    int year;
} Date;
Date d = {9, 1, 2005};

//一个匿名的结构 重命名为Date
typedef struct{
    int month;
    int year;
} Date;
//Strings是10个字符串的数组的类型
typedef char * Strings[10];

typedef struct node{
    int data;
    struct node *next;
} aNode;
//或
typedef struct node aNode;//这样用aNode就可以代替node

typedef语句那么长,分辨新名字的办法就是:最后的那个词就是--新名字

联合

和struct非常像

sizeof(union ...) =

sizeof(每个成员)的最大值
  • 存储

    • 所有成员共享一个空间
    • 同一时间只有一个成员是有效的
    • union的大小是其最大的成员
  • 初始化

    • 对第一个成员初始化
#include <stdio.h>
typedef union {
    int i;
    char ch[sizeof(int)];//有了4个字节的空间,这4个字节可以被看作i或者ch的数组
} CHI;
int main()
{
    CHI chi;
    int i;
    chi.i = 1234;//就会往那4个空间里放入1234的二进制码(我们用16进制表示为0x04D2)
    //chi.i = 1234 -> 0x04D2
    //内存中的样子我们猜应该是
    //--------------------
    //| 00 | 00 | 04 | D2|
    //--------------------
    //那就是说:ch[0] -> 00、ch[1] -> 00、ch[2] -> 04、ch[3] -> D2
    for (i=0; i<sizeof(int); i++) {
        printf("%02hhX", chi.ch[i]);//此处格式化意思:X->16进制,hh->1个字节别扩展了,02->显示两个数字不足补0
        //输出为:|D2|04|00|00|,因为X86CPU是小端机器,低位在前
    }
    /*
    以上是union的一个常用的场景,我可以通过这种方式得到一个整数内部的各个字节,同样用这种方式得到double,float的内部各个字节,这是很有趣的一个工具,用处比如:当我们要做文件操作的时候,当我们要把一个整数以二进制的形式写到一个文件中的时候,这就是我们可以用来作读写的一个中间媒介
    */
    printf("\n");
    return 0;
}

可变数组

可变数组的特点

  • 可以长大

  • 得到大小

  • 访问当前单元

怎么做?我们可以自己定义一个函数库(the interface)

  • Array array_create(int init_size)创建

  • void array_free(Array *a)回收空间

  • int array_size(const Array *a)数组里有多少单元

  • int* array_at(Array *a, int index)访问某个单元

  • void array_inflate(Array *a, int more_size)数组长大

typedef struct {
    int *array;
    int size;
} Array;
//如果
typedef struct {
    int *array;
    int size;
} *Array;//那么在后面使用Array
Array a;//那么其实a就是一个指针,这样在后面声明Array类型的指针变量就方便些
/*但是使用*Array这种别名的方式,有缺点。
1.那就是我再也无法在本地声明一个Array类型的本地变量了,因为它总是指针;
2.后面的人看到 Array a;总会误以为它就是本地变量,但实际上它是个指针
所以不建议这么做
*/

array.h文件

#ifndef _ARRAY_H_
#define _ARRAY_H_

typedef struct {
    int *array;
    int size;
} Array;
Array array_create(int init_size);
void array_free(Array *a);
int array_size(const Array *a);
int* array_at(Array *a, int index);
void array_inflate(Array *a, int more_size);
#endif

文件array.c

#include "array.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct {
//    int *array;
//    int size;
//} Array;
const BLOCK_SIZE = 20;
Array array_create(int init_size)
{
    Array a;
    a.size = init_size;
    a.array = (int*)malloc(sizeof(int)*a.size);
    return a;
}
void array_free(Array *a)
{
    free(a->array);
    a->array = NULL;
    a->size = 0;
}
//这里为什么把a->size放到函数里面了,不在外面直接使用,这叫封装,好处是把实现细节封装起来,以后业务如果变化了直接修改这里就行,方便
int array_size(const Array *a)
{
    return a->size; 
}
int* array_at(Array *a, int index)
{
    if (index > a->size) {
        //array_inflate(a, index-a->size);//不能这么做,一点点加比较浪费资源
        //可以一次给多点
        array_inflate(a, (index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
        //index 102
        //102/20=5 -> 5+1=6 -> 6×20=120
    }
    return &(a->array[index]);
}
void array_inflate(Array *a, int more_size)
{
    int *p = (int*)malloc(sizeof(int)(a->size + more_size));
    int i;
    for (i=0; i<a->size; i++) {
        p[i] = a->array[i];
    }
    free(a->array);
    a->array = p;
    a->size += more_size;
}

int main()
{
    Array a = array_create(100);
    printf("%d\n", array_size(&a));
    printf("%d\n", array_at(&a, 0));
    //为什么array_at要返回一个指针而不是直接返回单元的值?
    *array_at(&a, 0) = 10;//因为返回指针可以这样做单元赋值
    int number=0;
    int cnt=0;
    while(number != -1){
        scanf("%d", &number);
        if(number != -1)
            *array_at(&a,cnt++) = number;
    }
    array_free(&a);
    return 0;
}

可变数组的缺陷

1.长大的时候拷贝花时间

2.每次重申请的内存都是在后面重新要一块更大的内存,前面的free掉一大片都是浪费了

解决:

连接起来内存,不拷贝了

链表

文件node.h

#ifndef _NODE_H_
#define _NODE_H_
//定义节点
typedef struct _node {
    int value;
    struct _node *next;//_node类型的指针
} Node;
#endif

文件linked-list.c

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
int main()
{
    Node * head = NULL;
    int number;
    do{
        scanf("%d", &number);
        if(number != -1){
            //add to linked-list
            Node *p = (Node*)malloc(sizeof(Node));
            p->value = number;
            p->next = NULL;
            //find last one
            Node * last = head;
            if (last) {
                while (last->next) {
                    last = last->next;
                }
                //attach
                last->next = p;
            } else {
                head = p;
            }
        }
    } while (number != 1);
    return 0;
}

把链表封装到函数中

step1. 修改文件linked-list.c内容

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
void add(Node* head, number)

int main()
{
    Node * head = NULL;
    int number;
    do{
        scanf("%d", &number);
        if(number != -1){
            add(head, number);
        }
    } while (number != 1);
    return 0;
}

void add(Node* headP, number)
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = headP;
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        headP = p;//问题1:此处的headP和main函数中的head不是一个指针,光在这里修改没用啊
    }
}

问题1:此处的headP和函数外面的head不是一个指针,外面的head指针没被修改就没啥用啊!

setp2.进一步修改修改文件linked-list.c内容

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
void add(Node* head, number)

Node* headP;//增加全局变量的方式解决问题1,但是这样会引发问题2:全局变量是有害的
    
int main()
{
    Node * head = NULL;
    int number;
    do{
        scanf("%d", &number);
        if(number != -1){
            add(head, number);
        }
    } while (number != 1);
    return 0;
}

void add(Node* headP, number)
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = headP;
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        headP = p;
    }
}

问题2:全局变量是有害的,目前程序中就add函数会用到headP,而且如果我有多个链表呢?那么此时headP只能维护处理一个链表,无法解决多个链表。所以不应该使用全局变量的方案

step3.进一步修改修改文件linked-list.c内容,不使用全局变量

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
Node* add(Node* head, number)

//Node* headP;//不使用全局变量
    
int main()
{
    Node * head = NULL;
    int number;
    do{
        scanf("%d", &number);
        if(number != -1){
            head = add(head, number);//此处使用add返回一个新的指针赋值给head,但是会出现问题3:如果调用add的程序员忘记左边使用head赋值了,就会出现错误。
        }
    } while (number != 1);
    return 0;
}

Node* add(Node* headP, number)
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = headP;
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        headP = p;
    }
    return headP;//返回Node类型指针
}

这样add函数就可以支持多个链表了

问题3:如果调用add的程序员忘记左边使用head变量赋值了,就会出现错误。我们没办法强迫程序员必须使用一个变量来接受add函数的返回值

step4.进一步修改修改文件linked-list.c内容,不使用函数返回指针的方案

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
void add(Node** head, number)
    
int main()
{
    Node * head = NULL;
    int number;
    do{
        scanf("%d", &number);
        if(number != -1){
            add(&head, number);
        }
    } while (number != 1);
    return 0;
}

void add(Node** headP, number)//使用指针的指针传入
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = *headP;//这样在内部的修改就都能影响到外面的指针了
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        *headP = p;
    }
}

以上是使用指针的指针来实现链表函数

还有另一个方案

step5.进一步修改修改文件linked-list.c内容,使用自定义数据类型的方案来实现链表函数

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
typedef strcut _list {
    Node* head;
} List;

void add(List* pList, number)
    
int main()
{
    List list;
    int number;
    list.head = NULL;//初始化head指针
    do{
        scanf("%d", &number);
        if(number != -1){
            add(&list, number);
        }
    } while (number != 1);
    return 0;
}

void add(List* pList, number)//这和上面传指针的指针不是一回事吗?问题4.好处是什么?
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = pList->head;//*
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        pList->head = p;//*
    }
}

问题4.好处是什么?

1.我们用了自己定义的数据结构类型List来代表整个链表。这样更方便,清晰

2.方便以后扩展自定义的链表类型。目前List中只有一个head,但是以后会有扩充,比如,上面代码有一个循环去找最后一个节点(node)的指针next。这样做是不是比较麻烦?我们可以直接使用一个指针保存最后一个节点的地址不就ok了吗

step6.进一步修改修改文件linked-list.c内容,扩充自定义类型,来优化代码质量

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
typedef strcut _list {
    Node* head;
    Node* tail;//扩展了自定义链表类型
} List;

void add(List* pList, number)
    
int main()
{
    List list;
    int number;
    list.head = NULL;//初始化head指针
    list.tail = NULL;//初始化tail指针
    do{
        scanf("%d", &number);
        if(number != -1){
            add(&list, number);
        }
    } while (number != 1);
    return 0;
}

void add(List* pList, number)
{
    /**这题不会暂时放着
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    pList->head.next = p;
    pList->tail = p;//最后的节点
    */
}

链表的搜索

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
typedef struct _list {
    Node* head;
} List;

void add(List* pList, number)
void print(List* pList)  
int main()
{
    List list;
    int number;
    list.head = NULL;
    do{
        scanf("%d", &number);
        if(number != -1){
            add(&list, number);
        }
    } while (number != 1);
    //打印链表
    print(&list);
    //搜索数据是否在链表中
    int inputNumber;
    scanf("%d", &inputNumber);
    if (search(&list, inputNumber)){
        printf("找到了\n");
    }
    return 0;
}

void add(List* pList, number)
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = pList->head;//*
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        pList->head = p;//*
    }
}
void print(List* pList)
{
    Node *p;
    for (p=list.head; p; p=p->next) {
        printf("%d\t", p->value);
    }
    printf("\n");
}
void search(List* pList, number)
{
    Node *p;
    int isFound = -1;
    for (p=list.head; p; p=p->next) {
        if(p->value == number){
            isFound = 1;
            break;
        }
    }
    return isFound;
}

链表的删除

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
typedef struct _list {
    Node* head;
} List;
void delete(List* qList, number);
int main()
{
    List list;
    int number;
    list.head = NULL;
    do{
        scanf("%d", &number);
        if(number != -1){
            add(&list, number);
        }
    } while (number != 1);
    //删除链表元素
    
    
    return 0;
}

void add(List* pList, number)
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = pList->head;//*
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        pList->head = p;//*
    }
}
void delete(List* qList, number)
{
    /*
    Node* q;
    Node* p;
    for(q=NULL,p=qList->head; p; q=p, p=p->next){
        if(p->value == number){
            q->next = p->next;
            free(p);
            break;
        }
    }
    */
    //以上代码有个问题,p是否NULL有for第二个条件语句保障,但是q是否NULL没人保障,
    //for的第一个循环,q就是NULL,如果第一个循环就找到了number,那么q->value这句会报错
    //改进
    Node* q;
    Node* p;
    for(q=NULL,p=qList->head; p; q=p, p=p->next){
        if(p->value == number){
            if(q){
                q->next = p->next;
            }else{
                qList->head = p->next; 
            }
            free(p);
            break;
        }
    }
}

链表的清除

#incldue "node.h"
#include <stdio.h>
#include <stdlib.h>
//typedef struct _node {
//    int value;
//    struct _node *next;
//} Node;
typedef struct _list {
    Node* head;
} List;
void delete(List* qList, number);
int main()
{
    List list;
    int number;
    list.head = NULL;
    do{
        scanf("%d", &number);
        if(number != -1){
            add(&list, number);
        }
    } while (number != 1);
    //清除链表
    Node* q;
    Node* p;
    for (p=list.head, p, p=q) {
        q = p->next;
        free(p);
    }
    
    return 0;
}

void add(List* pList, number)
{
    //add to linked-list
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    //find last one
    Node * last = pList->head;//*
    if (last) {
        while (last->next) {
            last = last->next;
        }
        //attach
        last->next = p;
    } else {
        pList->head = p;//*
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 一.初识C语言 C语言的起源 1972年,贝尔实验室的丹尼斯.里奇和肯.汤普逊在开发UNIX操作系统时设计了C语言...
    Crtd_Code阅读 478评论 0 0
  • 1.什么是数据类型 基本类型数据 整数 整型 int 4 短整型 ...
    定格r阅读 776评论 0 0
  • 1.C语言中的数据类型 常量:就是程序中不可变化的量,是不可以被赋值的。通过#define定义常量,在C语言中叫做...
    zhoucanhui阅读 384评论 0 0
  • #C语言的基础学习 标签: c语言 数据类型 基本程序 第一章:C程序初步学习 1.怎么去编译C程序 gcc xx...
    精彩极限1阅读 679评论 0 0
  • 一、简述 对于C语言基础相关方面的表面理解,简单介绍。 二、二进制 生活中常用的是十进制,基数0,1,2,3,4,...
    曹元_阅读 302评论 1 7