C语言郝斌老师视频学习笔记(按照自己掌握情况做的重点笔记)

【暂时还不熟练的程序学生信息系统、冒泡、链表】

12P

1、运行第二个程序要点close workplace
2、要移植程序只要拷贝文件 *.cpp

16P

1、程序运行:编译,链接生成一个.exe的可执行文件 然后点 !请求cpu执行这个文件

17P

1、枚举是复合数据类型

18P

int = 3;//3最终是存放在内存中,程序终止之后3所占的空间被释放掉

19P

1、变量的本质是内存条中的一段空间

20P

1、当软件运行完毕后,操作系统将回收该内存空间,但操作系统
并不清空该内存空间中遗留下来的数据(即垃圾数据),所以我们通常为变量、数组分配好内存后要初始化。

进制:

                                                         【10】                              【17】
1、二进制:逢二进一(0,1,10,11,100,101,110,111,1000,1001,1010,1011,1100,1101,1110,1111,10000,10001)
2、十进制:逢十进一(0,1,2, 3,  4,  5,  6,  7,  8,    9,   10 ,11, 12, 13, 14,  15, 16,  17)即通过8421码与二进制来记忆
3、八进制:逢八进一(0,1,2, 3,  4,  5,  6,  7,  10,  11,  12, 13, 14, 15, 16,  17,  20, 21)
4、16进制:逢16进一(0,1,2, 3,  4,  5,  6,  7,  8,    9,   A,   B,   C,   D,  E,    F,   10,  11,...1F,20,...2F)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【重点】
【总结】十进制快速转二进制:

  1. 8421码(适用数值较小)
  2. 先转为十六进制,再将每一位数值分别以二进制的每四位换算,左边不够位补零

例1:

二进制:0101
十进制:5
三进制:12

例2:

12F + 1  =130  //逢十六进一       

例3:

五进制24的十进制为:2*5+4=14

例4:

    #include <stdio.h>
    int main(void)
    {
        int i = 0x32c;//等于12+2*16+3*16*16

        printf("i=%d\n",i);
        /*
            printf的用法
            %d表示以十进制输出
            %x或者%X表示以十六进制输出
            %o表示以八进制输出(!注意:这里与常量的整数八进制【0数字】区分开来,八进制数字前面是零不是字母o)
        */
        return 0;
    }

33P

例1、

    #include <stdio.h>
    int main(void)
    {
        int x = 47;
        
        printf("%x\n",x);//输出2f
        printf("%X\n",x);//输出2F
        printf("%#X\n",x);//输出0X2F
        printf("%#x\n",x);//输出0x2f
        
        printf("i = %d, j = %d\n",j, k);//推荐

        return 0;
    }

39P

【scanf】

1、用法一:scanf("输入控制符",输入参数);
   用法二:scanf("非输入控制符输入控制符",输入参数);//这里的非输入控制符必须原样输入

2、%输出\输入控制符&取地址符

3、使用scanf之前最好先使用printf提示用户以什么样的方式来输入

4、scanf中尽量不要使用非输入控制符,尤其是不要用\n

5、应该编写代码对用户的非法输入做适当的处理【即清除前面遗留下来的垃圾数据】

    while ((ch = getchar())  ! = '\n')
        continue;

44P

1. C语言对真假的处理
    非零是真
    零是假

2.  真是1表示
    假是0表示

49P

3.5%2 是不可以这样写的,数学上要求除数和被除数是整数

53P

1.if的范围问题:

    if (表达式)
        语句A;
        语句B;
    解释:if默认只能控制语句A的执行或不执行
            if无法控制语句B的执行或不执行
            或者说:语句B一定会执行

总结:if默认的只能控制一个语句的执行或不执行

2.

if  (表达式)
    {
    语句A;
    语句B;
    }
此时if可以控制语句A和语句B

由此可见:

if默认只能控制一个语句的执行或不执行
如果想控制多个语句的执行或不执行
就必须把这些语句用{}括起来

54P

1、如果新建文件时,软件会自动会添加后缀名.cpp  或 .c  。所以如果命名中含有符号点(.)则会编译等功能用不了则要手动加后缀才行

55P

1、调试错误且出现错误时按F4可以找到错误原因
2、全选代码之后按住alt然后按F8即可格式化代码

59P

1、程序 = 算法(解决方法的步骤) + 语言(C、java)  //所以一个问题上来先写他的解题思路(用伪代码写完在转化成语言代码)

60P

1、如何看懂一个程序,分三步:

  • (1)流程

  • (2)每个语句的功能

  • (3)试数

    eg:1/1+1/2+3+…+1/100

    #include <stdio.h>
    int main(void)
    {
        int i;
        float sum = 0;
        for(i = 1;i <= 100;i++)
            sum = sum + 1/(float)i;//float必须用%f输出
            //更简单的写法是:sum = sum + 1.0/i
        printf("sum = %f\n",sum);
        return 0;
    }		

试数过程:

1->  i = 1   1<=100   成立
    sum = 0+1/1.00 = 1    ++i      i = 2
2->  i =2    2<=100   成立
    sum = 1+1/2.0         ++i      i = 3
3->  i = 3   3<=100   成立
    sum = 1+1/2.0+1/3.0   ++i      i = 4

2、如何学习一些需要算法的程序【如何掌握一个程序】

(1)尝试自己去编程解决它

  • 但要意识到大部分人都是自己无法解决的,这是不要气馁,
  • 如果十五分钟还是想不出来,此时我建议您就可以看答案了

(2)如果解决不了,就看答案

  • 关键是把答案看懂,这个要花很大的精力,也是我们学习的重点
  • 看懂一个程序要分三步:流程、每个语句的功能、试数

(3)看懂之后尝试自己去修改程序,并且知道修改之后程序的不同输出结果的含义

  • 不建议看懂程序之后就立即自己敲程序

(4)照着答案去敲

(5)调试错误

(6)不看答案,自己独立把答案敲出来

(7)如果程序实在无法彻底理解,就把它背会,不过无法彻底理解的程序很少

72P

【浮点数的存储所带来的问题:】

1、float 和 double 都不能保证可以精确的存储一个小数,因为存储的是数值的近似值

举例:
float i = 99.9;
printf("%f\n",i);
最终在Vc++6.0的输出结果是:99.900002
因为浮点数无法准确存储,所以就衍生出来两个编程问题:
举例1:有一个浮点型变量x,如何判断x的值是否为零
    
if(|x-0.000001| <= 0.000001)    //加=是因为x=0时等号成立
    是零
else
    不是零

总结:x与一个非常小的值的差的绝对值小于等于这个值就表示x等于0

2、eg:本程序不对,因为循环中【更新的变量】(i)不能定义成浮点型

        float i;
        float sum = 0;
        for(i=1.0; i<=100;++i)
        {
                sum = sum + 1/i
        }

3、求1~100的奇数的平均数

    #include <stdio.h>
    int main(void)
    {
        int i;
        int sum = 0;
        int cnt = 0;
        float avg;//average的缩写
        
        for(i=1;i<101;++i)
        {
            if(i%2 == 1)
            {
                sum+=i;
                cnt++;
            }
        }
        avg = 1.0*sum/cnt;//1.0默认double类型,赋给float会丢失精度
        printf("cnt = %d\n",cnt);
        printf("sum = %d\n",sum);
        printf("avg = %f\n",avg);

        return 0;
    }

74P

4、多层for循环嵌套使用执行顺序

在这里插入图片描述

75P

1.十进制转r进制:除r取余,直至商为0,余数倒序排列

2. 1/2=0; 1%2=1;

76P

1.前自增和后自减的异同:

  • 相同:最终都使i的值加1
  • 不同:
    前自增整体表达式的值是i加1之后的值
    后自增整体表达式的值是i加1之前的值

2.为什么会出现自增

代码更加精炼、自增的速度更快

3.学习自增要明白的几个问题

1.我们编程时应该尽量屏蔽掉前自增和后自增的差别
2.自增表达式最好不要作为一个更大的表达式的一部分来使用
或者说
i++和++i单独成一个语句,不要把它作为一个完整复合语句的一部分来使用
如:
int m = i++ + ++i + i + i++;//这样写不但是不规范的代码,而且是不可移植的代码
printf("%d %d %d",i++,i++,i);//同上

78P

1.逗号表达式:

  • 格式:(A,B,C,D)
  • 功能:从左到右执行,最终表达式的值是最后一项的值

83P

1.for和while的比较

  • for和while可以相互转换
    即:
    for(1;2;3)
    A;
    等价于
    1while(2)
    {
        A;
        3;
    }
总结:虽然可以相互转换,但for的逻辑性更强,更不容易出错,推荐多使用for

84P

1.

12/10=1;    1/10=0;    10%12=10;     0%2=0;

2.斐波拉序列1,2,3,5,8…

#include <stdio.h>
int main(void)
{
    int n;
    int f1,f2,f3;
    int i;

    f1 = 1;
    f2 = 2;

    printf("请输入你需要的项的序列:");
    scanf("%d",&n);
    if (1 == n)
    {
        f1 = 1;
    }
    else if(2 == n)
    {
        f2 = 2;
    }
    else
    {
        for(i=3;i<=n;i++)
        {
            f3 = f2 + f1;
            f1 = f2;
            f2 = f3;
        }
    }
    printf("%d\n",f3);
    return 0;
}
  • 试数:假如 n = 6
- 1>    i=3     3<=6 成立 f3=1+2=3    f1=2    f2=3   
- 2>    i=4     4<=6 成立 f3=2+3=5    f1=3    f2=5   
- 3>    i=5     5<=6 成立 f3=3+5=8    f1=5    f2=8    
- 4>    i=6     6<=6 成立 f3=5+8=13   f1=8    f2=13   
- 5>    i=7     7<=6 不成立

89P

1.一元二次方程(do…while实现人机交互)

#include <stdio.h>
#include <math.h>
int main(void)
{
	double a, b, c;	
	double delta;
	double x1, x2;	
	char ch;

	do	
	{	
		printf("请输入一元二次方程的三个系数:\n");
		printf("a = ");
		scanf("%lf", &a);
		
		printf("b = ");
		scanf("%lf", &b);
		
		printf("c = ");
		scanf("%lf", &c);
		
		delta = b*b - 4*a*c;
		
		if (delta > 0)
		{
			x1 = (-b + sqrt(delta)) / (2*a);
			x2 = (-b - sqrt(delta)) / (2*a);
			printf("有两个解,x1 = %lf, x2 = %lf\n", x1, x2);
		}
		else if (0 == delta)
		{
			x1 = x2 = (-b) / (2*a);
			printf("有唯一解,x1 = x2 = %lf\n", x1, x2);
		}
		else
		{
			printf("无实数解!\n");
		}

		printf("您想继续么(Y/N): ");
		scanf(" %c", &ch);  //%c前面必须得加一个空格 原因略
	} while ('y'==ch || 'Y'==ch);
	
	return 0;
}

92P

1.break的用法

for (i=0;i<3;++i>)
{
    if(3>2)
    break;//break虽然是if内部语句,但break终止的却是for循环
    printf("嘿嘿!\n");//永远不会输出
}

for(i=0;i<=3;++i)
{
    for(j=1;j<=4;++j)
        break;//break只能终止最里面包裹它的那个循环
    printf("同志们好!\n");
}

3.break的用法

# include <stdio.h>
int main()
{ 
	int x=1, y=0, a=0, b=0;
	switch(x) // 第一个switch 
	{  
	case 1:  
		switch(y)  // 第二个switch 
		{  
		case 0:  
			a++; 
			break; //终止的是第二个switch  
		case 1:  
			b++; 
			break;
		}
		b = 100;
		break;   //终止的是第一个switch
	case 2: 
		a++;    
		b++;
		break;  
	}	
	printf("%d %d\n",a,b); //26行
	
	return 0;
}
//最终输出结果是:1,100

总结:

  • 在多层switch嵌套中,break只能最里面包裹它的switch
  • break不能直接用于if,除非if属于循环内部的一个字句

93P

1.continue的用法:

  • 用于跳过***本次循环***余下的语句,转去判断是否需要执行***下次***循环

96P

1.流程控制总测试

  • (1)
    在这里插入图片描述
  • (2)while默认控制一个语句
    在这里插入图片描述
  • (3)
    在这里插入图片描述
  • (4)
    在这里插入图片描述

99P

1.为什么需要数组

  • 为了解决大量同类型数据的存储和使用的问题
  • 为了模拟现实世界

101P

1.有关一维数组的操作

初始化

  • 完全初始化 int a[5] = {1,2,3,4,5}
  • 不完全初始化 int a[5] = {1,2,3} 未被初始化的自动为零
  • 不初始化,所有元素都是垃圾值 int a[5];
  • 清零 int a[5] = {0};

错误写法:

int a[5];
a[5] = {1,2,3,4,5};//错误
只有在定义数组的同时才可以整体赋值,
其他情况下整体赋值都是错误的

在这里插入图片描述

102P

1.二维数组的输出

for(i=0;i<3;i++)
{
    for(j=0;j<4;j++)
        printf("%d  ",a[i][j]);
    printf("\n");
}

103P

1.多维数组:

n维数组可以当作每个元素是n-1维数组的一维数组

106P

1.为什么需要函数

避免了重复性操作
有利于程序的模块化
  • 另:C语言中的模块化实质是以函数为基本单位(如下图示)
    在这里插入图片描述

107P

1.什么是函数

能够完成特定功能的独立的代码块

  • (1)
//max是函数的名字,i和j是形式参数,简称形参
//void表示函数没有返回值
#include <stdio.h>
void max(int i,int j)
{
	if(i>j)
		printf("%d\n",i);
	else
		printf("%d\n",j);
}

int main(void)
{
	int a, b, c, d, e, f;
	
	a = 1, b = 2; c = 3, d = 9, e = -5, f = 100;
	max(a,b);
	max(c,d);
	max(e,f);
	
	return 0;
}
  • (2)
# include <stdio.h>
int f(void)  //括号中的void表示该函数不能接受数据  int表示函数返回值是int类型的数据
{
	return 10; //向主调函数返回10
}

void g(void)  //8行  函数名前面的void表示该函数没有返回值
{
//	return 10; //error 与8行行首的void相矛盾
}

int main(void)
{
	int j = 88;

	j = f();
	printf("%d\n", j);

//	j = g(); //error 因为g函数没有返回值
	return 0;
}
  • (3)
# include <stdio.h>
int f()
{
	return 10.5;  //因为函数的返回值类型是int  所以最终f返回的是10而不是10.5
}

int main(void)
{
	int i = 99;
	double x = 6.6;
	
	x = f();
	printf("%lf\n", x);
	
	return 0;
}
  • (4)
    在这里插入图片描述
    (5)函数的分类
  • 1.有参函数和无参函数
  • 有返回值函数和无返回值函数
  • 库函数和用户自定义函数
  • 值传递函数和地址传递函数
  • 普通函数和主函数(main函数)
    总结:
    一个程序必须有且只有一个主函数
    主函数可以调用普通函数,普通函数不能调用主函数
    普通函数可以相互调用
    主函数是程序的入口,也是程序的出口

111P

  • 使用函数来判断素数(只能被1或本身整除)
#include <stdio.h>
bool IsPrime(int val)
{
	int i;

	for(i = 2;i<val;i++)//1可以整除任何数,所以要用val除以2~val
	{
		if(val%i == 0)//不能加到val就不是素数
			break;
	}
	if(i == val)//能加到val就是素数
		return true;
	else
		return false;
}
int main(void)
{
	int m;
	scanf("%d",&m);

	if(IsPrime(m))
		printf("Yes!\n");
	else
		printf("No!\n");
	
	return 0;
}

113P

1.函数调用和函数定义的顺序

【如果函数调用写在了函数定义的前面,则必须加函数前置声明】

函数前置声明:

1.告诉编译器即将可能出现的若干个字母代表的是一个函数
2.告诉编译器即将可能出现的若干个字母所代表的函数的形参和返回值的具体情况
3.函数是一个语句,末尾必须加分号
4.对库函数的声明是通过#include<库函数所在的文件的名字.h>来实现的
  • 例1.
# include <stdio.h>

void f(void); //函数声明, 分号不能丢掉

int main(void)
{
	f();

	return 0;
}

void f(void)
{
	printf("哈哈!\n");
}
  • 例2.
/*
一定要明白该程序为什么是错误的
一定要明白该程序第9行生效之后程序为什么就正确了
*/
# include <stdio.h>

//void f(void);  //9行

void g(void)
{
f(); //因为函数f的定义放在了调用f语句的后面,所有语法出错
}

void f(void)
{
printf("哈哈!\n");
}

int main(void)
{
g();

return 0;
}

115P

1.形参和实参

  • 个数相同
  • 位置一 一对应
  • 数据类型必须相互兼容

2.如何在开发中合理的设计函数来解决实际问题

  • (1)一个函数的功能尽量独立、单一
  • (2)多学习,多模仿牛人的代码

例1:

/*
    判断一个数字是否是素数
    只用一个函数实现,不好,代码的利用率不高	
*/
# include <stdio.h>

int main(void)
{
    int val;
    int i;

    scanf("%d", &val);
    for (i=2; i<val; ++i)//这里i代表2~val-1之间的除数
    {
        if (0 == val%i)
            break;
    }
    if (i == val)
        printf("Yes!\n");
    else
        printf("No!\n");

    return 0;
}

例2:

/*
    判断一个数字是否是素数
    用单独的函数来实现, 代码的可重用性提高	
*/
# include <stdio.h>

bool IsPrime(int val)
{
    int i;

    for (i=2; i<val; ++i)
    {
        if (0 == val%i)
            break;
    }
    if (i == val)
        return true;
    else
        return false;
}

int main(void)
{
    int val;
    int i;

    scanf("%d", &val);
    if ( IsPrime(val) )
        printf("Yes!\n");
    else
        printf("No!\n");

    return 0;
}

例3:

/*
	求1到某个数字之间(包括该数字)所有的素数,并将其输出
	只用main函数实现,有局限性:
		1. 代码的重用性不高
            也就是说下面的例子中
            如果要求下一个数字g的话又得要把val改成g了,
            下个数字重复如此
		2. 代码不容易理解		
*/
# include <stdio.h>

int main(void)
{
	int val;
	int i;
	int j;

	scanf("%d", &val);
	for (i=2; i<=val; ++i)
	{
		//判断i是否是素数,是输出,不是不输出
		for (j=2; j<i; ++j)//用i除以j(2~i-1)
		{
			if (0 == i%j)
				break;
		}
		if (j == i)
			printf("%d\n", i);
	}

	return 0;
}
- 试数:val=5
1>  i=2 2<=5 成立 j=2 2<2 不成立 2=2 成立 输出i=2 i=3
2>  3<=5 成立 j=2 2<3 成立   3%2=1 不成立 j=3
3>  i=3 3<3  不成立 3=3 成立 输出i=3 i=4
4>  4<=5 成立 j=2 2<4 成立   4%2=0成立 4!=2 不成立 i=5 
5>  i=5 5<=5 成立 j=2 2<5 成立 5%2=1 不成立 j=3
6>  i=5 3<5  成立 5%3=2 不成立 j=4
7>  i=5 4<5  成立 5%4=1 不成立 j=5
8>  i=5 5<5  不成立 5=5 输出i=5 i=6
9>  6<=5 不成立 结束!


例4:

/*
求1到某个数字之间(包括该数字)所有的素数,并将其输出
用1个函数来判断一个数字是否是素数
优点:
    代码比  如何设计函数_3.cpp  更容易理解
    代码的可重用性比  如何设计函数_3.cpp 高
    因为通过IsPrime()来判断i是否是素数
缺点:
    可重用性仍然不是非常高,
    比如有1000个数字,**求它们每个数字从1到它本身的素数**
    则
    for (i=2; i<=val; ++i)
    {
        if ( IsPrime(i) )
            printf("%d\n", i);
    }
    要写1000次,因为这个功能是通过for循环来达到的,所以这个功能(任意发送一个值,把1至该值之间的素数输出)也要用另一个函数来实现(也就是将功能通过函数来细化)
*/
# include <stdio.h>

bool IsPrime(int m)
{
	int i;
		
	for (i=2; i<m; ++i)
	{
		if (0 == m%i)
			break;
	}
	if (i == m)
		return true;
	else
		return false;

}

int main(void)
{
	int val;
	int i;
 
	scanf("%d", &val);
	for (i=2; i<=val; ++i)
	{
		if ( IsPrime(i) )
			printf("%d\n", i);
	}

	return 0;
}

例5:

/*
	用两个函数来实现求1到某个数字之间所有的素数,并将其输出
	本程序 和 如何合理设计函数_4.cpp 相比较
	代码量更少,可重用性更高
*/
# include <stdio.h>

//本函数的功能是: 判断m是否是素数,是返回true,不是返回false
bool IsPrime(int m)
{
	int i;
		
	for (i=2; i<m; ++i)
	{
		if (0 == m%i)
			break;
	}
	if (i == m)
		return true;
	else
		return false;

}
//本函数的功能是把1到n之间所有的素数在显示器上输出
void TraverseVal(int n)
{
	int i;

	for (i=2; i<=n; ++i)
	{
		if ( IsPrime(i) )
			printf("%d\n", i);
	}
}

int main(void)
{
	int val;
 
	scanf("%d", &val);
	TraverseVal(val);

	return 0;
}

总结:函数是C语言的基本单位,类是java、C#、C++的基本单位

117P

1.常用的系统函数

double sqrt(double x);//求x的平方根

120P

1.变量的作用域和存储方式:

按作用域分:

全局变量:
    在所有函数外部定义的变量叫全局变量
    全局变量适用范围:从定义位置开始到整个程序结束
局部变量:
    在一个函数内部定义的变量或者函数的形参都统称为局部变量
    void f(int i)
    {
        int j = 20;
    }
    i和j都属于局部变量
    局部变量使用范围:只能在本函数内部使用
    
注意的问题:
    - 全局变量和局部变量命名冲突的问题
    - 在一个函数内部如果定义的局部变量的名字和全局变量名一样时,局部变量会屏蔽掉全局变量

按变量的存储方式:

静态变量
自动变量
寄存器变量

121P

1.指针初次简介

例1:

# include <stdio.h>

int main(void)
{
	int * p; //p是变量的名字, int * 表示p变量存放的是int类型变量的地址
	int i = 3;

	p = &i;  //OK
	//p = i; //error,因为类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值
	//p = 55; //error 原因同上

	return 0;
}

指针的分类:

  1. 基本类型指针(重点)

  2. 指针和数组

  3. 指针和函数

  4. 指针和结构体(重点)

  5. 多级指针

  6. 基本类型指针(重点)

例2:

# include <stdio.h>
int main(void)
{
    int * p;//p是变量的名字, int * 表示p变量存放的是int类型变量的地址
            //int * p; 不表示定义了一个名字叫做*p的变量
            // int * p; 应该这样理解: p是变量名, p变量的数据类型是 int *类型
            //所谓int * 类型 实际就是存放int变量地址的类型	
    int i = 3;
    int j;

    p = &i;
        /*
        1. p保存了i的地址, 因此p指向i,因此*p就是i
        2. p不是i,i也不是p,更准确的说: 修改p的值不影响i的值,修改i的值也不会影响p的值,因为它两的类型不同
        3. 如果一个指针变量指向了某个普通变量, 则
            *指针变量  就完全等同于  普通变量
        例子:	
            如果p是个指针变量,并且p存放了普通变量i的地址
            则p指向了普通变量i【即通过p就可以找到i】
            *p 	就完全等同于  i
            或者说:  在所有出现*p的地方都可以替换成i
                    在所有出现i的地方都可以替换成*p

    //重点:【   *p 就是以p的内容为地址的变量   】
        */
    j = *p;  //等价于 j = i;
    printf("i = %d, j = %d\n", i, j);

    return 0;
}

122P

1.指针的重要性

  • 表示一些复杂的数据结构(树、图等)
  • 快速的传递数据,减少了内存的耗用(发送一个数据通过函数来处理)【重点】
  • 使函数返回一个以上的值(return只能返回一个值)【重点】
  • 能直接访问硬件(内存单位的一些信息,地址)
  • 能够方便的处理字符串(后面讲)
  • 是理解面向对象语言中的引用的基础

总结:指针是C语言的灵魂

123P

- 指针的定义
    <地址>
        内存单元的编号
        从零开始的非负整数
        范围:4G【0~4G-1】

    <指针>
        指针就是地址,地址就是指针
        指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
        指针的本质就是一个操作受限的非负整数(只能相减)

【指针和指针变量的关系】
    指针就是**地址**,地址就是指针
    地址就是内存单元的编号
    指针变量是存放地址的**变量**
    指针和指针变量是两个不同的概念
    但是要注意: 通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样

1.通过硬件进一步了解指针【CPU对内存条进行控制通过三根线图示】

在这里插入图片描述

图示说明:
    ①先把内存条的数据读入CPU,CPU内部进行处理
    ②把处理的结果在写入内存条
    ③当内存条数据量达到一定程度时它会一次性写入硬盘中去
- 控制线:控制数据传输方向(读入/写入)
- 数据线:传输数据
- 地址线:确定对内存条那一个单元进行控制

2.CPU通过地址总线来控制内存条(一般32根地址总线,每根有0和1两种状态,所以最终可以控制232个单元(字节),每个单元(字节)是8位的,所以最终内存可以存232*8位)

在这里插入图片描述

126P

1.基本类型指针常见错误解析

例1:

# include <stdio.h>

int main(void)
{
	int * p; //p是变量的名字, int * 表示p变量存放的是int类型变量的地址
	int i = 3;

	p = &i;  //OK
	//p = i; //error,因为类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值
	//p = 55; //error 原因同上

	return 0;
}

例2:

# include <stdio.h>

int main(void)
{
	int i = 5;
	int * p;//写*p意味着以p里面的垃圾值(二进制)为地址的一个单元(我们不知道的一个单元,p里面是一个二进制代码)
    *p = i;//程序不可以越过系统来读取和访问一个不属于它的内存单元,所以会报错,只访问不改就是窃取信息

    printf("%d\n",*p);

	int * q;

	p = &i;
	//*q = p; //error 语法编译会出错,不能将int *赋给(转换成)int 
	//*q = *p;  //error  *q没有给赋值,是垃圾值
	p = q;  //q是垃圾值,q赋给p, p也变成垃圾值
	printf("%d\n", *q);   //13行
    /*
    q的空间是属于本程序的,所以本程序可以读写q的内容(二进制地址), 
    但是如果q内部是垃圾值,则本程序不能读写*q的内容(变量垃圾值)
    因为此时*q所代表的内存单元的控制权限并没有分配给本程序
    所以本程序运行到13行时就会立即出错
    */
	return 0;
}
  • 【野指针java的时候讲指针悬挂的问题】

    多个指针指向一个动态内存,问题要解决有多少个指针指向它,程序一旦一个都不free()就越跑越慢(内存泄漏),多free()一个程序就崩溃
    在这里插入图片描述
    例3:【经典指针程序——互换两个数字】

# include <stdio.h>

//前置声明(不写形参)
void huhuan_1(int , int);
void huhuan_2(int *, int *);
void huhuan_3(int *, int *);

int main(void)
{
	int a = 3;
	int b = 5;
	
	huhuan_3(&a, &b); //huhuan_2(*p, *q); 是错误的, huhuan_2(a, b);也是错误的
	printf("a = %d, b = %d\n", a, b);//这里只能是主函数的a,b   

	return 0;
}

//不能完成互换功能
void huhuan_1(int a, int b)//改变的是形参的值跟主函数实参没关系,再者这个函数运行完形参也释放没有
{
	int t;

	t = a;
	a = b;
	b = t;

	return;//一般加说明程序完了
}

//不能完成互换功能(仅仅换了p和q的内容[地址值],不会影响a,b的值)
void huhuan_2(int * p, int * q)
{
	int * t;//如果要互换p和q的值,则t必须是int *,不能是int,否则会出错

	t = p;
	p = q;
	q = t;
}

//可以完成互换功能(通过换地址值来实现变量的交换)
void huhuan_3(int * p, int * q)//【p是a的地址,*p就是a,同理q也是,所以改变*p和*q的值也就改了a和b的值,即指向互换之后才才在内存中释放的】
{
	int t; //如果要互换*p和*q的值, 则t必须定义成int,不能定义成int *, 否则语法出错

	t = *p;  //p是int *,  *p是int,因为*p代表的是以p的内容为地址的变量
	*p = *q;
	*q = t;
}

128P 星号的三种含义

  • 的含义:
    1. 乘法
    2. 定义指针变量
      int * p;
      //定义了一个名字叫p的变量,int *表示p只能存放int类型变量的地址
    3. 指针运算符
      • 该运算符放在已经定义号的指针变量的前面
      • 如果p是一个定义好的指针变量
      • 则 *p表示以p的内容为地址的变量

130P 指针使函数返回一个以上的举例

  1. 实参必须为该普通变量的地址
  2. 形参必须为指针变量
  3. 在被调函数中通过
    *形参名= …
    的方式就可以修改主调函数相关变量的值
# include <stdio.h>

int f(int i, int j)
{
	return 100;
//	return 88;
}

void g(int * p, int * q)
{
	*p = 1;
	*q = 2;
}

int main(void)
{
	int a = 3, b = 5;
	g(&a, &b);
	printf("%d %d\n", a, b);
	return 0;
}

【2. 指针和数组】

132P 下标和指针关系概述

# include <stdio.h>

int main(void)
{
	int a[5]; //a是数组名  5是数组元素的个数 元素就是变量  a[0]  -- a[4]
//	int a[3][4]; //3行4列 a[0][0]是第一个元素 a[i][j]第i+1行j+1列
	int b[5];
	
	//a = b;//error a是常量,地址常量
	printf("%#X\n", &a[0]);
	printf("%#X\n", a);

	return 0;
}
/*
	在Vc++6.0中的输出结果是:
--------------
0X12FF6C
0X12FF6C
Press any key to continue
--------------
总结:
	一维数组名:
		一维数组名是个指针常量
		它存放的是一维数组第一个元素的地址
*/
# 下标和指针关系:
    
    如果p是个指针变量,则
     【p[i]永远等价于 *(p+i)(重点)

    如:int *p;
        int i [4];
        p = i; 
        *(p+x)==i[x];

    即:*(p+1)==i[1];
# include <stdio.h>

int main(void)
{
	int a[5] = {1,2,3,4,5};
	int i;

	for (i=0; i<5; ++i)
		printf("%d\n", a[i]);  //a[i] == *(a+i) 

	return 0;
}

133P 一维数组名的含义

  1. 一位数组名是个指针常量
  2. 它存放的是一位数组第一个元素的地址

134P 确定一个一维数组需要2个参数及其原因

  • 需要两个参数:

    1. 数组第一个元素的地址
    2. 数组的长度
  • 例1:

# include <stdio.h>

//f函数可以输出任何一个一维数组的内容
void f(int * pArr, int len)
{
	int i;

	for (i=0; i<len; ++i)
		printf("%d  ", *(pArr+i) );  //*pArr *(pArr+1) *(pArr+2) 
	printf("\n");
}

int main(void)
{
	int a[5] = {1,2,3,4,5};
	int b[6] = {-1,-2,-3,4,5,-6};
	int c[100] = {1, 99, 22, 33};

	f(a, 5);  //a是 int *
	f(b, 6);
	f(c, 100);

	return 0;
}
  • 例2:
/*
	一定要明白 10行的pArr[3] 和17行 19行的a[3] 是同一个变量
*/

# include <stdio.h>

void f(int * pArr, int len)
{
	pArr[3] = 88;  //10行
}

int main(void)
{
	int a[6] = {1,2,3,4,5,6};
	
	printf("%d\n", a[3]);  //17行
	f(a, 6);
	printf("%d\n", a[3]);  //  19行
    //由于a存储了数组a第一个元素的地址,所以*a = a[0];即a[3]=*(a+3)
	return 0;
}
/*
	在Vc++6.0中的输出结果是:
---------------------------
4
88
Press any key to continue
---------------------------
*/

图解:
在这里插入图片描述

# include <stdio.h>

void f(int * pArr, int len)
{
	int i;
	for (i=0; i<len; ++i)
		printf("%d  ",  pArr[i]);  //*(pArr+i) 等价于 pArr[i]  也等价于 b[i] 也等价于 *(b+i) 

	printf("\n");
}

int main(void)
{
	int b[6] = {-1,-2,-3,4,5,-6};

	f(b, 6);

	b[i]

	return 0;
}

136P 指针变量的运算

  1. 指针变量不能相加,不能相乘,也不能相除
  2. 如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量才可以相减
# include <stdio.h>

int main(void)
{
	int i = 5;
	int j = 10;
	int * p = &i;
	int * q = &j;
	int a[5];
	p = &a[1];
	q = &a[4];
	printf("p和q所指向的单元相隔%d个单元\n", q-p);

	//p - q 没有实际意义

	return 0;
}

137P 一个指针变量到底占几个字节的问题(非重点)

预备知识:

  • sizeof(数据类型)

  • 功能:返回值就是该数据类型所占的字节数

  • 例子:sizeof(int) = 4; _______sizeof(char) = 1;_______sizeof(double) = 8;

  • sizeof(变量名)

  • 功能:返回值是该变量所占的字节数
    假设p指向char类型变量(1个字节)

    假设q指向int类型变量(4个字节),
    假设r指向double类型变量(8个字节)
    p q r 本身所占字节数是否一样?
    答案:一样,都是4个字节

    总结:一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节。
    一个变量的地址使用该变量首字节地址来表示

#include <stdio.h>
int main(void)
{
	char ch = 'A';
	int i = 99;
	double x = 66.6;
	char *p = &ch;
	int * q = &i;
	double *r = &x;

	printf("%d %d %d\n",sizeof(ch),sizeof(i),sizeof(x));//1	  4	  8
	printf("%d %d %d\n",sizeof(p),sizeof(q),sizeof(r));//4	 4	 4

	return 0;
}
  • 图示:内存条存储单元大小存储的时候都需要4个字节,因为它们的编号(地址)全都是用32根线表示的
    在这里插入图片描述

专题:动态内存分配(重点)

传统数组的缺点

  1. 数组长度必须事先指定,且只能是常整数,不能是变量
    例子:
    int a [5];//OK

    int len = 5;
    int a [len];//error

  2. 传统形式定义的数组,该数组的内存程序员无法手动释放在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,知道该函数运行完毕,数组的空间才会被系统释放

  3. 数组的长度一旦定义,其长度就不能再更改,
    数组的长度不能在函数运行的过程中动态的扩充或缩小

  4. A函数定义数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕之后,A函数中的数组将无法在被其他函数使用
    传统方式定义的数组不能跨函数使用

为什么需要动态分配内存

动态数组很好的解决了传统数组的着4个缺陷
传统数组也叫静态数组

动态内存分配举例__动态数组的构造

  • malloc用法_1
//	malloc 是 memory(内存) allocate(分配)的缩写

# include <stdio.h>
# include <malloc.h>  //不能省

int main(void)
{
	int i = 5; //分配了4个字节 静态分配   11 行
	int * p = (int *)malloc(4); //12行
		/*
			1. 要使用malloc函数,必须添加malloc.h这个头文件
			2. malloc函数只有一个形参,并且形参是整型
			3. 4表示请求系统为本程序分配4个字节
			4. malloc函数只能返回第一个字节的地址
			5. 12行分配了8个字节, p变量占4个字节, p所指向的内存也占4个字节
			6. p本身所占的内存是静态分配的, p所指向的内存是动态分配的   	
		*/

	*p = 5; //*p 代表的就是一个int变量, 只不过*p这个整型变量的内存分配方式和11行的i变量的分配方式不同
	free(p); //free(p)表示把p所指向的内存给释放掉  p本身的内存是静态的,不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放 
	printf("同志们好!\n");

	return 0;
}
  • malloc用法_2
# include <stdio.h>
# include <malloc.h>

void f(int * q)
{
	//*p = 200; //error
	//q = 200;
	//**q = 200;  //error
	*q = 200;
	//free(q);  //把q所指向的内存释放掉  本语句必须的注释掉,否则会导致第20行的代码出错
}

int main(void)
{
	int * p = (int *)malloc(sizeof(int)); //sizeof(int)返回值是int所占的字节数
	*p = 10;

	printf("%d\n", *p);  //10
	f(p);  //p是int *类型
	printf("%d\n", *p);  //200    第20行

	return 0;
}

动态一维数组示例

# include <stdio.h>
# include <malloc.h>

int main(void)
{
	int a[5]; //如果int占4个字节的话,则本数组总共包含有20个字节,每四个字节被当做了一个int变量来使用
	int len;
	int * pArr;
	int i;

	//动态的构造一维数组
	printf("请输入你要存放的元素的个数: ");
	scanf("%d", &len);
	pArr = (int *)malloc(4 * len);  //第12行  本行动态的构造了一个一维数组, 该一维数组的长度是len, 该数组的数组名是pArr, 该数组的每个元素是int类型  类似于 int pArr[len];

	//realloc(pArr,100);//原先50则扩大,且包含原先前50个数据,原先150则缩小,且原先后50个数据将丢失
	//对一维数组进行操作,  如:对动态一维数组进行赋值
	for (i=0; i<len; ++i)
		scanf("%d", &pArr[i]);

	//对位一维数组进行输出
	printf("一维数组的内容是:\n");
	for (i=0; i<len; ++i)
		printf("%d\n", pArr[i]);
	
	free(pArr); //释放掉动态分配的数组

	return 0;
}
  • 动态构造一维数组示意图_1
    在这里插入图片描述
  • 动态构造一维数组示意图_2

【pArr存储前面四个字节的第一个字节的地址,但由于系统由 int * 知道pArr指向前四个字节(类似地还有其他数据类型),如 后面结构体中*Student同理】
在这里插入图片描述

静态内存和动态内存比较【重点】

  1. 静态内存是由系统自动分配,由系统自动释放
    静态内存是在栈分配的

  2. 动态内存是由程序员手动分配,手动释放
    动态内存是在堆分配的

跨函数使用内存的问题【重点】–即在一个函数内部修改另一个函数中的值只能发送它的地址,包括结构体等

  1. 静态内存不可以跨函数使用
    所谓静态内存不可以跨函数使用更准确的说法是:
    • 静态内存在函数执行期间可以被其他函数使用
    • 静态内存在函数执行完毕之后就不再比其他函数使用了
  2. 动态内存可以跨函数使用
    • 动态内存在函数执行完毕之后仍然可以被其他函数使用

多级指针

  • 多级指针_1
# include <stdio.h>

int main(void)
{
	int i = 10;
	int * p = &i;  //p只能存放int类型变量的地址
	int ** q = &p;  //q是int **类型, 所谓int **类型就是指q只能存放int *类型变量的地址, 	
	int *** r = &q;  //r是int ***类型, 所谓int ***类型就是指r只能存放int ** 类型变量的地址, 

	//r = &p;  //error 因为r是int *** 类型,r只能存放int **类型变量的地址
	printf("i = %d\n", ***r); //输出结果是10  只有 ***r才表示的是i, *r或 **r或 ****r代表的都不是i

	return 0;
}
  • 多级指针_2
void f(int ** q)
{
	//*q就是p	
}

void g()
{
	int i = 10;
	int * p = &i;类型

	f(&p); //p是int *类型 , &p是int ** 类型
}

int main(void)
{
	g();
	return 0;
}
  • 多级指针 讲课示意图
    在这里插入图片描述

跨函数使用内存的问题

  • 静态变量不能跨函数使用
# include <stdio.h>

void f(int ** q) //q是个指针变量,无论q是什么类型的指针变量,都只占4个字节
{
	int i = 5;
	//*q等价于p  q和**q都不等价于p
	//*q = i; //error 因为*q = i; 等价于 p = i; 这样写是错误的
	*q = &i;  // p = &i;
}

int main(void)
{
	int *p;  //13行
	
	f(&p);
	printf("%d\n", *p);  //16行  本语句语法没有问题,但逻辑上有问题,函数f()已经释放*q,*p不能再被访问,p可以被访问

	return 0;
}
  • 动态内存可以跨函数使用示例_1
# include <stdio.h>
# include <malloc.h>

void f(int ** q)
{
	*q = (int *)malloc(sizeof(int)); //sizeof(数据类型) 返回值是该数据类型所占的字节数
	//等价于 p = (int *)malloc(sizeof(int));
	//q = 5; //error
	//*q = 5; //p = 5;
	**q = 5; //*p = 5;
}

int main(void)
{
	int * p;

	f(&p);
	printf("%d\n", *p);
	
	return 0;
}

跨函数使用内存的问题试题

8、下程序中,能够通过调用函数fun,使main函数中的指针变量p指向一个合法的整型单元的是 (C)
A) main()
  {  int *p;
     fun(p);} 
  int fun(int *p)
  { int s; 
    p=&s;
  }

B) main()
   { int *p;
     fun(&p);}
  int fun(int **q)
  {  int s;
     *q=&s;
  }

C) #include <stdlib.h>
   main()
   {  int *p;
      fun(&p);}
   int fun(int **q)
   { 
        *q=(int *)malloc(4);
   }

D) 
   #include <stdlib.h>
   main() 
  {  int  *p;
     fun(p);}
   int fun(int *p)
   { 
        p=(int *)malloc(sizeof(int));
   }
                        二级C 2003年4月45题

注释: 这个题很有意思。考查了:

  1. 指针的指针的使用
  2. 动态内存分配与自动变量的内存分配。 动态分配的内存必须调用free()函数才能释放,而静态变量一旦跳出它的代码作用范围,就会由编译器自动释放掉。

让我们先看:
A) 选项无论fun()中p的值如何变化,都不会影响到主函数中p的值,因为它是值传递

B) 选项倒是把p的地址&p传递给了fun()函数,但遗憾的是,由于s是个静态变量,当推出fun()函数后,s变量所占内存单元会被会被释放掉,此时主函数中的p还是没法指向一个合法的int型单元

C) 选项fun()的形参 int **p;表明p是个指向指针变量的指针变量,即是个指针的指针。 而主函数中的 int *p; 表明p只是个指针变量,但&p则指向了p,&p也是个指向指针变量p的指针变量,实参和形参类型一致。 fun()的功能是使实参p指向了一个int型变量, 又由于该int型变量是由malloc()动态分配的,所以推出fun()函数并不会影响实参p的指向, 故C是对

D) 选项犯了和A同样的错误。 真想不到二C还会考到这个知识,哈哈!

结构体

为什么需要结构体

为了表示一些复杂的事物,而普通的基本类型无法满足实际需求

什么叫结构体

把一些基本类型数据组合在一起形成一个新的复合数据类型,这个叫做结构体
# include <stdio.h>

struct Student
{
	int age;
	float score;
	char sex;
};

int main(void)
{
	struct Student st = {80, 66.6, 'F'};	
/*	int age;
	float score;
	char sex;
	int age2;
	float score2;
	char sex2;
*/
	return 0;
}

如何定义结构体[3种方式]

3种方式推荐使用第一种

# include <stdio.h>

//第一种方式,这里只是定义了一个新的数据类型,并没有定义变量
struct Student
{
	int age;
	float score;
	char sex;
};//分号不能省

//第二种方式
struct Student2
{
	int age;
	float score;
	char sex;
} st2;

//第三种方式
struct 
{
	int age;
	float score;
	char sex;
} st3;

int main(void)
{
	struct Student st = {80, 66.6, 'F'};

	return 0;
}

怎么使用结构体变量

赋值初始化

  • 定义的同时可以整体赋初值
  • 如果定义完之后,则只能单个的赋初值
# include <stdio.h>

//第一种方式
struct Student
{
	int age;
	float score;
	char sex;
};

int main(void)
{
	struct Student st = {80, 66.6, 'F'}; //初始化  定义的同时赋初值
	struct Student st2;
	st2.age = 10;
	st2.score = 88;
	st2.sex = 'F';

	printf("%d %f %c\n", st.age, st.score, st.sex);
	printf("%d %f %c\n", st2.age, st2.score, st2.sex);
	

	return 0;
}

如何取出结构体变量中的每一个成员【重点】

  1. 结构体变量名.成员名
  2. 指针变量名->成员名(第二种方式更常用)
    指针变量名->成员名:在计算机内部会转化成(*指针变量名).成员名的方式来执行
    所以说这两种方式是等价的

例子:

# include <stdio.h>

//第一种方式
struct Student
{
	int age;
	float score;
	char sex;
};

int main(void)
{
	struct Student st = {80, 66.6F, 'F'}; //初始化  定义的同时赋初值
	struct Student * pst = &st;  //&st不能改成st

	pst->age = 88;//第二种方式
	st.score = 66.6f; //第一种方式  66.6在C语言中默认是double类型,如果希望一个实数是float类型,则必须在末尾加f或F, 因此66.6是double, 66.6f或66.6F是float
	printf("%d %f\n", st.age, pst->score);	

	return 0;
}
  1. pst->age 在计算机内部会被转化成(*pst).age,没有什么为什么,这就是->的含义,这也是一种硬性规定
  2. 所以pst->age 等价于 (*pst).age 也等价于 st.age
  3. 我们之所以知道pst->age等价于st.age,是因为pst->是被转化成了(*pst).age来执行
  4. pst->age的含义:pst所指向的那个结构体变量中的age这个成员

结构体变量和结构体指针变量作为函数参数传递的问题

推荐使用结构体指针变量作为函数参数来传递
//	通过函数完成对结构体变量的输入和输出
# include <stdio.h>
# include <string.h>

struct Student
{
	int age;
	char sex;
	char name[100];
}; //分号不能省

void InputStudent(struct Student *);//前置声明可以不写形参名
void OutputStudent(struct Student ss);
int main(void)
{
	struct Student st;  //15行

	InputStudent(&st); //对结构体变量输入  必须发送st的地址
//	printf("%d %c %s\n", st.age, st.sex, st.name);
	OutputStudent(st); //对结构体变量输出  可以发送st的地址也可以直接发送st的内容

	return 0;
}

void OutputStudent(struct Student ss)
{
	printf("%d %c %s\n", ss.age, ss.sex, ss.name);
}

void InputStudent(struct Student * pstu) //pstu只占4个字节
{
	(*pstu).age = 10;
	strcpy(pstu->name, "张三");
	pstu->sex = 'F';	
}

/*
//本函数无法修改主函数15行st的值 所以本函数是错误的
void InputStudent(struct Student stu)
{
	stu.age = 10;
	strcpy(stu.name, "张三");  //不能写成 stu.name = "张三";
	stu.sex = 'F';
}
*/

发送地址还是发送内容

/*
	示例:
		发送地址还是发送内容
	目的:
		指针的优点之一:
			快速的传递数据,
			耗用内存小
			执行速度快
*/
# include <stdio.h>
# include <string.h>

struct Student
{
	int age;
	char sex;
	char name[100];
}; //分号不能省

void InputStudent(struct Student *);
void OutputStudent(struct Student *);
int main(void)
{
	struct Student st ;  //15行
	//printf("%d\n", sizeof(st));

	InputStudent(&st); //对结构体变量输入  必须发送st的地址
	OutputStudent(&st); //对结构体变量输出  可以发送st的地址也可以直接发送st的内容 但为了减少内存的耗费,也为了提高执行速度,推荐发送地址,为防止在输出过程中有被改写数据的危险,可在前面加  const  关键字来限定不能改写

	return 0;
}

void OutputStudent(struct Student *pst)
{
	printf("%d %c %s\n", pst->age, pst->sex, pst->name);
}

void InputStudent(struct Student * pstu) //pstu只占4个字节
{
	(*pstu).age = 10;
	strcpy(pstu->name, "张三");
	pstu->sex = 'F';	
}

/*
//本函数无法修改主函数15行st的值 所以本函数是错误的
void InputStudent(struct Student stu)
{
	stu.age = 10;
	strcpy(stu.name, "张三");  //不能写成 stu.name = "张三";
	stu.sex = 'F';
}
*/

结构体变量的运算

    结构体变量不能相加,不能相减,也不能相除;
    但结构体变量可以相互赋值
    如:struct Student st1,st2;
        st1 = st2;或st2 = st1;

冒泡排序

# include <stdio.h>

//冒泡排序【掌握程序可以通过试数了解】
void sort(int * a, int len)
{
	int i, j, t;

	for (i=0; i<len-1; ++i)
	{
		for (j=0; j<len-1-i; ++j)
		{
			if (a[j] > a[j+1])  // >表示升序 <表示降序
			{
				t = a[j];
				a[j] = a[j+1];
				a[j+1] = t; 
			}
		}
	}
}

int main(void)
{
	int a[6] = {10, 2, 8, -8, 11, 0};
	int i = 0;

	sort(a, 6);

	for (i=0; i<6; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

举例:

  • 动态构造存放学生信息的结构体数组【重点】【可以自己用方法改进】
# include <stdio.h>
# include <malloc.h>

struct Student
{
	int age;
	float score;
	char name[100];
};

int main(void)
{
	int len;
	struct Student * pArr;
	int i, j;
	struct Student t;
	

	//动态的构造一维数组
	printf("请输入学生的个数:\n");
	printf("len = ");
	scanf("%d", &len);
	pArr = (struct Student *)malloc(len * sizeof(struct Student));
	
	//输入
	for (i=0; i<len; ++i)
	{
		printf("请输入第%d个学生的信息:\n", i+1);
		printf("age = ");
		scanf("%d", &pArr[i].age);

		printf("name = ");
		scanf("%s", pArr[i].name);  //name是数组名,本身就已经是数组首元素的地址, 所以pArr[i].name 不能改成 &pArr[i].name

		printf("score = ");
		scanf("%f", &pArr[i].score);
	}

	//按学生成绩升序排序 冒泡算法
	for (i=0; i<len-1; ++i)
	{
		for (j=0; j<len-1-i; ++j)
		{
			if (pArr[j].score > pArr[j+1].score) //>升序 <降序
			{
				t = pArr[j];
				pArr[j] = pArr[j+1];
				pArr[j+1] = t;
			}
		}
	}

	printf("\n\n学生的信息是:\n");
	//输出
	for (i=0; i<len; ++i)
	{
		printf("第%d个学生的信息是:\n", i+1);
		printf("age = %d\n", pArr[i].age);
		printf("name = %s\n", pArr[i].name);  //name是数组名,本身就已经是数组首元素的地址, 所以pArr[i].name 不能改成 &pArr[i].name
		printf("score = %f\n", pArr[i].score);
	
		printf("\n");
	}

	return 0;
}

枚举

什么是枚举

把一个事物所有可能的取值一一列举出来
# include <stdio.h>

//只定义了一个数据类型,并没有定义变量, 该数据类型的名字是 enum WeekDay
enum WeekDay
{
	MonDay, TuesDay, WednesDay, ThursDay, FriDay, SaturDay, SunDay
};

int main(void)
{
	//int day; //day定义成int类型不合适
	enum WeekDay day = SunDay;
	printf("%d\n", day);

	return 0;
}

怎么使用枚举

/*
	对枚举的应用!
*/
# include <stdio.h>

enum weekday
{
	MonDay, TuesDay, WednesDay, ThursDay, FriDay, SaturDay, SunDay //考虑将WednesDay改为WednesDay = 5之后,MonDay与SunDay的值是多少?
};

void f(enum weekday i) //本函数的目的只是期望接受0~6之间的数字,将形参i定义为枚举型,可以有效的避免传参失误的问题
{
	switch (i)
	{
	case 0:
		printf("MonDay!\n");
		break;
	case 1:
		printf("TuesDay!\n");
		break;
	case 2:
		printf("WednesDay!\n");
		break;
	case 3:
		printf("ThursDay!\n");
		break;
	case 4:
		printf("FriDay!\n");
		break;
	case 5:
		printf("SaturDay!\n");
		break;
	case 6:
		printf("SunDay!\n");
		break;  
	}          
}

int main(void)
{
	f(FriDay); //虽然FriDay本质上就是5,但直接写成f(5);就是错的, 也不能写成f(10); 考虑为什么! 嘿嘿

	return 0;
}

枚举的优缺点

代码更安全
书写麻烦

补码

原码:
也叫	符号-绝对值码
最高位0表示正 1表示负,其余二进制位是该数字的绝对值的二进制位
原码简单易懂
加减运算复杂
存在加减乘除四种运算,增加了CPU的复杂度
零的表示不唯一
反码:
反码运算不便,也没有在计算机中应用
移码:
移码:表示数值平移n位,n称为移码量
移码:主要用于浮点数的阶码的存储
补码:
已知十进制求二进制
	求正整数的二进制
		除2取余,直至商为零,余数倒序排序

	求负整数的二进制
		先求与该负数相对应的正整数的二进制代码,然后将所有位取反,末尾加1,不够位数时,左边补零

	求零的二进制
		全是零

已知二进制求十进制
	如果首位是0,则表明是正整数,按普通方法来求

	如果首位是0,则表明是**负整数**
  • 将所有位取反,末尾加1就,所有数字就是该负数的绝对值,即取反加一是正数

    如果全是零,则对应的十进制数字就是零

  • 例子:
原码:1001010
补码:0110110
十六进制:(36)H
十进制:3*16^1+6*16^0=48=-54

# include <stdio.h>

int main(void)
{
//	int i = -100;
//	printf("%#X\n", i); //0XFFFFFF9C

	int j = 0xFFFFFFCA;
	printf("%d\n", j);//-54
	return 0;
}
//1001010
# include <stdio.h>

int main(void)
{
//	int i = -5;
//	printf("%#X\n", i);

//	int j = 0xFFFFFFF5;
//	printf("%d\n", j);

	char ch = 0x80;
//	printf("%d\n", ch);

	ch = 129;// 1000 0000
	printf("%d\n", ch);

	return 0;
}
  • 学习目标:

    1. 在Vc++6.0中一个int类型的变量所能存储的数字的范围是多少?

      int类型变量所能存储的最大正数用十六进制表示是:7FFFFFFF

      int类型变量所能存储的绝对值最大的负整数用十六进制表示是:80000000

    具体可以参见下图:
    在这里插入图片描述
    以下在上面都讲过:

    1. 最小负数的二进制代码是多少
    2. 最大整数的二进制代码是多少
    3. 已知一个整数的二进制代码求出原始的数字,数字超过最大正数会怎样
    4. 不同类型数据的相互转换

进制转换(前面20P已经讲)

字符串的处理(暂时不讲)

链表

算法:

1. 通俗的定义:
	解题的方法和步骤
2. 狭义定义:
	对存储数据的操作
	对不同的存储结构,要完成某一个功能所执行的操作时不一样的
	比如:
		要输出数组中的所有元素的操作和
		要输出链表中所有元素的操作肯定是不一样的
	这说明:
		【算法是依附于存储结构的
		不同的存储结构,所执行的的算法是不一样的】
3. 广义定义:
	广义的算法也叫泛型
	即:无论数据是如何存储的,对该数据的操作都是一样的
	如下图所示:
	分层思想:在高看都是线性,往低看细节就不一样

在这里插入图片描述

我们至少可以通过两种结构来存储数据

数组

  • 优点:
    存储速度快
  • 缺点:
    需要一个连续的很大的内存
    插入和删除元素的效率很低

链表

【专业术语】

首节点:存放第一个有效数据的节点
尾节点:存放最后一个有效数据的节点
头结点:头结点的数据类型和首节点的数据类型是一模一样的
		- 头结点是首节点前面的那个节点
		- 头结点并不存放有效数据
		- 设置头结点的目的是为了方便对链表的操作
头指针:存放头结点地址的指针变量

优点:
插入删除元素效率高
不需要一个连续的很大的内存

缺点:
查找某个位置的元素效率很低

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

struct Node
{
	int data; //数据域
	struct Node * pNext; //指针域
};

//函数声明
struct Node * create_list(void);
void traverse_list(struct Node *);

int main(void)
{
	struct Node * pHead = NULL; //等价于 struct Node * pHead = NULL;

	pHead = create_list();  //create_list()功能:创建一个非循环单链表,并将该链表的头结点的地址付给pHead
	traverse_list(pHead);
	
	return 0;
}

struct Node * create_list(void)
{
	int len;  //用来存放有效节点的个数
	int i;
	int val; //用来临时存放用户输入的结点的值

	//分配了一个不存放有效数据的头结点
	struct Node * pHead = (struct Node *)malloc(sizeof(struct Node));
	if (NULL == pHead)
	{
		printf("分配失败, 程序终止!\n");
		exit(-1);
	}
	struct Node * pTail = pHead;
	pTail->pNext = NULL;

	printf("请输入您需要生成的链表节点的个数: len = ");
	scanf("%d", &len);
	
	for (i=0; i<len; ++i)
	{
		printf("请输入第%d个节点的值: ", i+1);
		scanf("%d", &val);
		
		struct Node * pNew = (struct Node *)malloc(sizeof(struct Node));
		if (NULL == pNew)
		{
			printf("分配失败, 程序终止!\n");
			exit(-1);  //终止程序
		}
		pNew->data = val;
		pTail->pNext = pNew;
		pNew->pNext = NULL;
		pTail = pNew;
	}
	
	return pHead;
}

void traverse_list(struct Node * pHead)
{
	struct Node * p = pHead->pNext;

	while (NULL != p)
	{
		printf("%d  ", p->data);
		p = p->pNext;
	}
	printf("\n");
	
	return;
}

【造空/非空链表都要先做好头结点】
在这里插入图片描述
【链表遍历示意图】
在这里插入图片描述
【运行结果】
在这里插入图片描述

位运算符

&	--	按位与
		&& 逻辑与 也叫并且
		&& 与 & 的含义完全不同
		1&1 = 1
		1&0 = 0
		0&1 = 0
		0&0 = 0

		5&7 = 5		21&7 = 5
		5&1 = 1		5&10 = 0
	
|	--	按位或
		||逻辑或
		|按位或

		1|0 = 1
		1|1 = 1
		0|1 = 1
		0|0 = 0

~	--	按位取反
		~i 就是把i变量所有的二进制位取反

^	--	按位异或
		相同为零
		不同为1

		1^0 = 1
		0^1 = 1
		1^1 = 0
		0^0 = 0

<<	--	按位左移
		i>>3 表示把i的所有二进制位左移3位,右边补零
		左移n位相当于乘以2的n次方
		面试题:
			A)	i = i*8;
			B)	i = i<<3;
			请问上述两个语句,哪个语句的执行速度快
			答案:B快

>>	--	按位右移
		i>>3 表示把i的所有二进制位右移3位,左边一般补零,当然也可能补1(最高位是1)
		左移n位相当于除以2的n次方
		面试题:
			A)	i = i/8;
			B)	i = i>>3;
			请问上述两个语句,哪个语句的执行速度快
			答案:B快
# include <stdio.h>

int main(void)
{
	int i = 3;
	int j = 5;
	int k;

/*	k = i | j;
	printf("%d\n", k);//7

	k = ~i; //
	printf("%d\n", k);//-4
*/
	
	k = i << 1;
	printf("%d\n", k);//6

	return 0;
}
  • 位运算的现实意义:

    通过位运算符我们可以对数据的操作精确到每一位

  • 信息采集:知道a的第一位和第三位:a&0101得出的结果可以判断

# include <stdio.h>

int main(void)
{
	int i = -5;
	int j = 10;
	int k;

	k = i & j;
	printf("%d\n", k);

	k = i && j; //k的值只能是1或0, 因为&&是逻辑运算符, 逻辑运算符的结果只能是真或假, 在C中,真用1表示看,假用零表示
	printf("%d\n", k);

	return 0;
}

【运算过程】
在这里插入图片描述

暂不讲【从C的可以自己学习】

  • 文件
  • typedef

二进制全部为零的含义 --0000000000000 的含义

1. 数值零
2. 空字符结束标记符	'\0'
3. 空指针NULL
	NULL本质也是零,而这个零不代表数字零,而表示的是内存单元的编号的零

	我们计算机规定了,以零为编号的存储单元的内容不可读,也不可写(可能是很重要的数据,中断、异常等处理)
		eg:
		free(p);
		p = NULL; X
		*p = ...

在这里插入图片描述

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页