课程链接:郝斌老师C语言课程
本菜鸟研二开始从零学习嵌入式,先从C语言开始吧。整理一下郝斌老师的C语言笔记,方便我自己随时复习。
127、指针7_经典指针程序_互换两个数字
#include <stdio.h>
void huhuan_3(int *,int *)
/*
void huhuan(int a,int b)
{
int t;
t = a;
a = b;
b = t;
return;
}
*/
//不能完成互换功能
/*
void huhuan_2(int * p,int * q)
{
int * t;//如果要互换p和q的值,则t必须是int *,不能是int,否则会出错
t = p;
p = q;
q = t;
}
*/
//不能完成互换功能
int main()
{
int a = 3;
int b = 5;
huhuan_3(&a,&b);//huhuan(*p,*q);是错误的;huhuan(a,b);也是错误的
printf("%d\n%d",a,b);
return 0;
}
void huhuan_3(int * p,int * q)
{
int t;//如果要互换*p和*q的值,则t必须是int,不能是int*,否则会出错
t = *p;
*p = *q;
*q = t;
}
//可以完成互换功能
128、指针_8_星号的三种含义
*含义:
1、乘法
2、定义指针变量
int *p;//定义了一个名字叫做p的变量,int*表示p只能存放int变量的地址
3、指针运算符
该运算符放在已经定义好的指针变量的前面,如果p是一个已经定义好的指针变量,则*p表示以p的内容为地址的变量。
129、指针_9_复习前面所有指针知识 实参和形参永远是不同
1、基本类型指针_1
#include <stdio.h>
int main()
{
int * p; //等价于 int *p;也等价于int* p;
int i = 5;
char ch = 'A';
p = &i; //*p是以p的内容为地址的变量
*p = 99;
printf("i = %d,*p = %d\n",i,*p);
//p = &ch;
//p = ch;//error
//p = 5; //error
return 0;
}
2、基本指针类型_2
#include <stdio.h>
void huhuan_3(int *,int *)
/*
void huhuan(int a,int b)
{
int t;
t = a;
a = b;
b = t;
return;
}
*/
//不能完成互换功能
/*
void huhuan_2(int * p,int * q)
{
int * t;//如果要互换p和q的值,则t必须是int *,不能是int,否则会出错
t = p;
p = q;
q = t;
}
*/
//不能完成互换功能
int main()
{
int a = 3;
int b = 5;
huhuan_3(&a,&b);//huhuan(*p,*q);是错误的;huhuan(a,b);也是错误的
printf("%d\n%d",a,b);
return 0;
}
void huhuan_3(int * p,int * q)
{
int t;//如果要互换*p和*q的值,则t必须是int,不能是int*,否则会出错
t = *p;
*p = *q;
*q = t;
}
//可以完成互换功能
3、检测实参和形参是否同一个变量
#include <stdio.h>
void f(int i)
{
i = 99;
}
int main()
{
int i = 6;
printf("i = %d",i);
f(i);
printf("i = %d",i);
return 0;
}
130、指针_10_通过指针为什么可以使被被调函数修改主调函数多
指针使函数返回一个以上的值举例_1
#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()
{
int a = 3,b = 5;
g(&a,&b);
printf("a = %d\n,b = %d\n",a,b);
return 0;
}
如何通过被调函数修改主调函数普通变量的值?
1、实参必须为该普通变量的地址;
2、形参必须为指针变量;
3、在被调函数中通过 "*形参名 = ......" 的方式就可以修改主调函数相关变量的值。
131、指针_11_一维数组和指针关系概述
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列
a[3] = 5;
132、指针_12_下标和指针的关系
本节课缺失,引用评论区推荐的一篇文章指针和数组的关系
1、引用数组元素的方法:
①用数组下标引用数组元素
数组a中元素用下标表示为:
a[0] a[1] a[2] a[3] a[4]
②用指针引用数组元素
数组a中元素用下标表示为:
int *p = a;
*p, *(p+1), *(p+2), *(p+3), *(p+4)
2、数组和指针的关系:
①既然p是指向数组第一个元素起始地址的指针,可以用*(p+i)表示数组中第i+1个元素,a也是指向数组第一个元素的指针啊,那么能不能用*(a+i)表示第i+1个元素呢?
可以的,可以用printf 打印 *(a+i)的值验证
②反过来,a是指向数组第一个元素起始地址的指针,可以用a加数组下标引用数组元素,如a[3],p也是指向数组第一个元素起始地址的指针,能不能用p加数组下标引用数组元素?
也是可以的,可以用printf 打印 p[0], p[1]....的值验证
PS: 由上得,数组名a和指针p是一样的。实际上编译时,数组元素a[i] 就是当作 *(a+i)去处理的,先通过数组名a找到数组的首地址,然后首地址 a+i 得到元素a[i]的地址,
再然后通过*(a+i)得到第i个元素的内容。
所以:数组的第i个元素直接写成*(a+i)的形式直接取得值,效率不就提升了吗。省去编译器先通过数组名a找到数组的首地址,然后首地址 a+i 得到元素a[i]的地址,
再然后通过*(a+i)得到第i个元素的内容。
3、指向数组的指针的自增:
int a[5];
int *p = a;
可以 ++p 递增指针p指向下一个数组元素,然后用*p取得元素的值。
能不能用a++或者++a把指针指向下一个数组元素? 不能!!! 开头就说过,数组名是指向数组首元素的指针常量。
指针a是不可以指向其他元素的,只能指向首元素的起始地址。
4、数组指针做形参
把数组从主调函数传到被调函数时,我们可以传数组的首地址和数组长度就可以,为什么不是传递数组呢?还是为了效率,下面用代码说明:
#include <stdio.h>
#include <string.h> // 包含string.h以使用strlen函数
//strlen 函数是 C 语言标准库中的一个函数,用于计算以空字符('\0')结尾的字符串的长度。
void Output(char *p, int len);
int main()
{
char str[] = "HelloWorld HelloWorld HelloWorld HelloWorld HelloWorld HelloWorld ";
Output(str, strlen(str));
return 0;
}
void Output(char *p, int len)
{
for (int i = 0; i < len; ++i)
{
printf("%c", *(p + i));
}
printf("\n"); //
}
如上,如果我们Output函数不是用指针的方式char *p,而是用数组的方式char p[]的话,有什么不一样?
我们定义的数组str有65字节,调用函数的话会传递这65字节的数组。
char *p指针大小是4字节,只需要传递4字节的数据就可以了,不止少传递数据,执行起来也更快了。
133、指针_13_一维数组名的含义
一维数组名:
一维数组名是个指针常量,它存放的是一维数组第一个元素的地址
int main(void)
{
int a[5];
printf("%#X\n",&a[0]);
printf("%#X\n",a);
return 0;
}
134、指针_14_确定一个一维数组需要2个参数及其原因
1、确定一个一维数组需要几个参数_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()
{
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、确定一个一维数组需要几个参数_2
#include <stdio.h>
void f(int *pArr,int len)
{
pArr[3] = 88;//等价于*(pARR+3)
}
int main()
{
int a[6] = {1,2,3,4,5,6};
printf("%d\n",a[3]);//4
f(a,6);
printf("%d\n",a[3]);
return 0;
}
图解:
3、确定一个一维数组需要几个参数_3
#include <stdio.h>
//f函数可以输出任何一个一维数组的内容
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]
pArr[i]
printf("\n");
}
int main()
{
int b[6] = {-1,-2,-3,4,5,-6};
f(b,6);
return 0;
}
135、指针_15_复习上节课的知识
#include <stdio.h>
void OutArr(int *pArr,int len)
{
pArr[2] = 10;//pArr[2] == *(pArr+2) == *(a+2) = a[2]
int i;
for(i=0;i<len;++i)
printf("%d\n",pArr[i]);
}
int main()
{
int a[5] = {1,2,3,4,5};
OutArr(a,5);
//printf("%d\n",a[2]);
//a = &a[2];//error,因为a是常量
//printf("%#X,%#X\n",a,&a[0]);
return 0;
}
136、指针_16_指针变量的运算
指针变量不能想加,不能相乘,也不能相除。如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量才可以相减。
#include <stdio.h>
int main()
{
int i = 5;
int j = 10;
int *p = &i;
int *q = &j;
// p-q 没有实际意义,因为它们不指向同一个数组
int a[5];
p = &a[1]; // p 指向数组的第二个元素
q = &a[4]; // q 指向数组的最后一个元素
// 打印 p 和 q 所指向的单元相隔的元素个数
printf("p和q所指向的单元相隔%ld个单元\n", (long)(q-p)); // 使用 %ld 并转换为 long 类型
// p-q 表示它们之间的元素个数,因为它们指向同一个数组
return 0;
}
137、指针_17_何谓变量的地址 一个指针变量到底占几个字节
1、预备知识
sizeof (数据类型) 返回值就是该数据类型所占的字节数。
例子:sizeof (int) = 4 sizeof (char) = 1 sizeof (double) = 8
sizeof (变量名) 返回值就是该变量所占的字节数。
2、
假设p指向char类型变量(1个字节),q指向int类型(4个字节),r指向double类型(8个字节)
p、q、r本身所占的字节数是否一样?
p、q、r本身所占的字节数是一样的。
一个指针到底占几个字节的问题
#include <stdio.h>
int main()
{
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(p),sizeof(q),sizeof(r));
return 0;
}
由于 p
、q
和 r
都是指针类型,sizeof(p)
、sizeof(q)
和 sizeof(r)
在大多数现代平台上通常都会返回相同的值,因为指针类型的大小是固定的,与它们指向的数据类型无关。
这个值通常是 4 字节(32位系统)或 8 字节(64位系统)。
总结:
一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占4/8个字节。
一个变量的地址是用该变量首字节的地址来表示的。
138、指针_18_动态内存分配概述
动态内存分配
传统数组的缺点
为什么需要动态分配内存
动态内存分配举例_动态数组的构造
静态内存和动态内存的比较
跨函数使用内存的问题
139、指针_19_传统数组的缺点【重点】
1、数组长度必须事先制定,且只能是常整数,不能是变量。
例子:
int a[5]; //OK
int len = 5; int a [len]; //error
2、传统形式定义的数组,该数组的内存程序员无法手动释放
在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会释放。
3、数组的长度一旦定义,其长度不能再更改
数组的长度不能在函数运行的过程中动态的扩充或缩小。
4、A函数定义的数组在A函数运行期间可以被其它函数使用,但A函数运行完毕之后,A函数中的数组将无法在被其它函数使用。
#include <stdio.h>
void g(int pArr,int len)
{
pArr[2] = 88;//pArr[2] == a[2]
}
void f()
{
int a[5] = {1,2,3,4,5};//20个字节的存储空间程序员无法手动编程释放它,
//它只能在本函数运行完毕时由系统自动释放
g(a,5);
printf("%d\n",a[2]);
}
int main()
{
f();
return 0;
}
140、指针_20_为什么需要动态内存分配【重点】
动态数组很好的解决了传统数组的这股个缺陷。
传统数组也叫静态数组。
141、指针_21_malloc函数使用的简单介绍
/*malloc是memory(内存)allocate(分配)的缩写*/
#include <stdio.h>
#include <malloc.h>//不能省略
int main()
{
int i = 5;//分配了4个字节 静态分配//7行
int *p = (int *)malloc(4);//8行
/*
1、要使用malloc函数,必须添加malloc.h这个头文件
2、malloc函数只有一个形参,并且形参是整型
3、4表示请求系统为本程序分配4个字节
4、maloc函数只能返回第一个字节的地址
5、8行分配了8个字节,p变量占4个字节,p指向的内存也占4个字节
6、p本身所占的内存是静态分配,p指向的内存是动态分配的
*/
*p = 5;//*p代表的就是一个int变量,只不过*p这个整型变量的内存分配方式
//和7行的i变量的分配方式不同
free(p);//free p表示把p所指向的内存给释放掉 p本身的内存是静态的,不能由程序员手动释放,
//p本身的内存只能在p变量所在的函数运行终止时由系统自动释放
printf("同志们好!\n");
return 0;
}
142、指针_22_malloc函数的用法续
#include <stdio.h>
void f(int *q)
{
//*p = 200//error
//q = 200;
*q = 200;
//free(q);//把q所指向的内存释放掉 本语句必须注释掉,
//否则会导致第18行的代码出错
}
int main()
{
int *p = (int *)malloc(sizeof(int));//sizeof返回值是int所占的字节数
*p = 10;
printf("%d\n",*p);//10
f(p);
printf("%d\n",*p);//200
return 0;
}
143、指针_23_动态一维数组的构造
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a[5];//如果int占4个字节,则本数组总共包含有20个字节,
//每个字节被当作了一个int变量来使用
int len;
int *pArr;
int i;
//动态的构造一维数组
printf("请输入你要存放的元素的个数:");
scanf("%d",&len);
pArr = (int*)malloc(4*len);//本行动态的构造了一个一维数组,该一维数组的长度是len,
//该数组的元素是int类型,类似于int pArr[len];
//对一维数组进行操作
//对动态一维数组进行赋值
printf("请输入你想要存放的元素内容:\n");
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;
}
静态数组
动态数组
如果用户输入5,那么一个分配20个字节,pArr存放的是第一个字节的地址,因为pArr本身是int*类型,所有它指向前四个字节,这段代码的结果:系统找到了20字节内存的空间,把此空间分配给这个程序,并且pArr指向前四个字节。
realloc函数
void *realloc(void *ptr, size_t size);
ptr:指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配的。如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
size
:内存块的新的大小,以字节为单位。如果大小为 0,且 ptr
指向一个已存在的内存块,则 ptr
所指向的内存块会被释放,并返回一个空指针。
144、指针_24_动态内存和静态内存的比较【重点】
静态内存由系统自动分配,由系统自动释放,是在栈分配的。
动态内存是由程序员手动分配,手动释放,是在堆分配的。
145、指针_25_多级指针
#include <stdio.h>
int main()
{
int i = 10;
int *p = &i;
int **q = &p;
int ***r = &q;
//r = *p;//因为r是int ***类型,r只能够存放int **类型变量的地址
printf("i = %d\n",***r);
return 0;
}
#include <stdio.h>
void f(int **q)
{
//*q就是p
}
void g()
{
int i = 10;
int *p = &i;
f(&p);//p是int *类型,&p是int**类型
}
int main()
{
g(;)
return 0;
}
146、指针_26_复习上节课知识
#include <stdio.h>
#include <malloc.h>
//可以修改12行动态分配的这4个字节的内容
void f(int *q)
{
*q = 10;
}
void g(int **r)
{
}
int main()
{
int *p = (int*)malloc(4);//12行
printf("*p = %d\n",*p);
g(&p);//p是int *类型,&p是int **类型
printf("*p = %d\n",*p);
return 0;
}
148、指针_28_静态变量不能跨函数使用详解【重点】
跨函数使用内存_1
#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()
{
int *p;//13行
f(&p);
printf("%d\n",*p);//16行 本句语法没有问题,但逻辑上有问题,f中的存储空间已经释放
return 0;
}
149、指针_29_动态内存可以跨函数使用详解【重点】
#include <stdio.h>
void f(int **q)//q也是个指针变量,无论q是什么类型的指针变量,都只占4个字节
{
*q = (int*)malloc(sizeof(int));//sizeof(数据类型) 返回值是该数据类型所占的字节数
//等价于 p = (int*)malloc(sizeof(int));
//q = 5 //error
//*q = 5;//p = 5;
**q = 5;//*p = 5;
}
int main()
{
int *p;
f(&p);
printf("%d\n",*p);
return 0;
}
150、指针_30_跨函数使用内存习题测试【重点】
下程序中,能够通过调用函数fun,使main函数中的指针变量p指向一个合法的整型单元的是
A
int main()
{
int *p;
fun(p);
...
}
int fun(int *p)
{
int s;
p = &s;
}
B
int main()
{
int *p;
fun(&p);
...
}
int fun(int **q)
{
int s;
*p = &s;
}
C
#include <stdlid.h>
int main()
{
int *p;
fun(&p);
...
}
int fun(int **q)
{
*q = (int*)malloc(4);
}
D
int main()
{
int *p;
fun(p);
...
}
int fun(int *p)
{
*q = (int*)malloc(4);
}
151、结构体1_为什么需要结构体 什么叫结构体【重点】
为什么需要结构体?
为了表示一些复杂的事物,而普通的基本类型无法满足实际要求。
什么叫结构体?
把一些基本类型数据组合在一起形成一个新的复合数据类型,这个叫做结构体。
#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 name[100];
int age2;
float score2;
char name2[100];
*/
return 0;
}
152、结构体2_如何定义一个叫结构体【3种方式】
3种方式 推荐使用第一种
//这只是定义了一个新的数据类型,并没有定义变量
//第一种方式
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;
153、结构体3_怎样使用结构体变量概述
1、赋值和初始化
2、如何去除结构体变量中的每一个成员
3、结构体变量的运算
4、 结构体变量和结构体变量的指针作为函数参数传递的问题
举例:动态构造存放学生信息的结构体数组
154、结构体4_结构体是赋值和初始化
赋值和初始化
定义的同时可以整体赋初值。
如果定义完之后,则只能单个的赋初值。
#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;
}
155、结构体5_如何取出结构体变量中的每一个成员【重点】
1、结构体变量名.成员名
2、指针变量名—>成员名(更常用)
指针变量名—>成员名 在计算机内部会被转化成(*指针变量名).成员名的方式来执行。所以说这两种方式是等价的。
例子:
#include <stdio.h>
struct Student
{
int age;
float score;
char sex;
};
int main(void)
{
struct Student st = {80,66.6,'F'};//初始化 定义的同时赋初值
struct Student *pst = &st;//&st不能够改成st
pst->age = 88;//第二种方式
st.score = 66.6f;//第一种方式 66.6在C语言中默认是double类型,
//如果希望一个实数是float类型,则必须在末尾+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->age被转化为(*pst).age来执行。
(4)pst->age的含义:
pst所指向的那个结构体变量中的age这个成员。
156、结构体6_布置作业
157、考前知识点概述
158、结构体7_复习上节课知识
#include <stdio.h>
struct Student
{
int age;
char sex;
char name[100];
};//分号不能省略
int main()
{
struct Student st = {20,'F',"小娟"};
struct Student *pst = &st;
printf("%d %c %s\n",st.age,st.sex,st.name);
printf("%d %c %s\n",pst->age,pst->sex,pst->name);//pst->age转化成(*pst).age等价于st.age
return 0;
}
159、结构体8_通过函数对结构体变量的输入和输出
#include <stdio.h>
#include <string.h>
struct Student
{
int age;
char sex;
char name[100];
};//分号不能省略
void InputStudent(struct Student *stu);
void OutputStudent(struct Student ss)
int main()
{
struct Student st;
InputStudent(&st);//对结构体变量输入
//printf("%d %c %s\n",st.age,st.sex,st.name);
OutStudent(st);//对结构体变量输出 可以发送st的地址也可以直接发送st的内容
return 0;
}
void InputStudent(struct Student *pstu)//pstu只占4个字节
{
(*pstu).age = 10;
strcpy(pstu->name,"张三");
pstu->sex = 'F';
}
void OutputStudent(struct Student ss)//pstu只占4个字节
{
printf("%d %c %s\n",ss.age,ss.sex,ss.name);
}
/*
//本函数无法修改st的值,所以本函数是错误的
void InputStudent(struct Student stu)
{
stu.age = 10;
strcpy(stu.name,"张三");//不能写成 stu.name = "张三";
stu.sex = 'F';
}
*/
160、结构体9_应该发送内容还是应该发送地址【重点】
指针的优点之一:
快速的传递数据,耗用内存小,执行速度快
#include <stdio.h>
#include <string.h>
struct Student
{
int age;
char sex;
char name[100];
};//分号不能省略
void InputStudent(struct Student *stu);
void OutputStudent(struct Student *pst);
int main()
{
struct Student st;
printf("%d\n",sizeof(st));
InputStudent(&st);//对结构体变量输入
OutputStudent(&st);//对结构体变量输出 可以发送st的地址也可以直接发送st的内容
return 0;
}
void InputStudent(struct St udent *pstu)//pstu只占4个字节
{
pstu->age = 10;
strcpy(pstu->name,"张三");
pstu->sex = 'F';
}
void OutputStudent(struct Student *pst)//pstu只占4个字节
{
printf("%d %c %s\n",pst->age,pst->sex,pst->name);
}
161、指针优点大总结【重点】
162、结构体变量的运算
结构体变量不能相加减乘除,但结构体变量可以相互赋值。
truct Student
{
int age;
char sex;
char name[100];
};//分号不能省略
struct Student st1,st2;
st1+st2;st1-st2;st1*st2;st1/st2;//都是错误的
st1 = st2;st2 = st1;//都是正确的
163、冒泡排序
#include <stdio.h>
//冒泡排序
void sort(int *a, int len)
{
int i, j;
int 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; // 声明变量i
sort(a, 6);
for (i = 0; i < 6; ++i) // 使用已声明的变量i
printf("%d ", a[i]); // 打印数组的所有元素
printf("\n");
return 0;
}
164、结构体11_综合应用_学生管理系统(存储,排序,输出)
动态构造存放学生信息的结构体数组
动态构造一个数组,存放学生的信息,然后按分数排序输出
#include <stdio.h>
#include <stdlib.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));
//pArr是一个指向struct Student类型的指针变量,用来存储malloc分配的内存块的地址。
for(i=0;i<len;++i)
{
printf("请输入第%d个学生的信息:",i+1);
printf("age = ");
scanf("%d",&pArr[i].age);
printf("姓名是");
scanf("%s",pArr[i].name);//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("姓名是%s\n",pArr[i].name);
printf("score =%f\n",pArr[i].score);
}
// 释放动态分配的内存
free(pArr);
return 0;
}
165-166、枚举 上
什么是枚举
把一个事物所有可能的取值一一列举出来。
#include <stdio.h>
//定义了一个数据类型,并没有定义变量,该数据类型的名字是 enum WeekDay
enum WeekDay
{
MonDay, TuesDay, WednesDay,ThursDay, FirDay,SaturDay, SunDay
};
int main()
{
//int day;//day定义成int类型不合适
enum WeekDay day = WednesDay;
printf("%d\n",day);
return 0;
}
枚举的优缺点
代码更安全。
书写麻烦。
167、进制转换
生活中用到进制的例子:
一周七天:七进
一年十二个月:十二进
一小时六十分:六十进制
什么叫进制
进制就是逢几进一,我们说N进制实际就是逢N进一。
计算机只识别二进制,人类最习惯使用的是十进制。,为了实际需要,又建立了八进制和十六进制。C语言规定八进制前要加0,十六进制前要加0x或0X,十进制前什么都不加。
不同数制数的表示
在汇编中,在数字后加B表示二进制,加O表示八进制,加D表示十进制,加H表示十六进制。
什么叫n进制
十进制:十个基数,逢10进一;
基数:0 1 2 3 4 5 6 7 8 9
二进制:二个基数,逢2进一;
基数:0 1
八进制:二个基数,逢8进一;
基数:0 1 3 4 5 6 7
十六进制:二个基数,逢10进一;
基数:0 1 2 3 4 5 6 7 8 9 A B C D E F
进制转化
预备知识:
小数除以大数,则商为0 余数是小数本身
如:1/2 = 0 余数1
2/2 = 1 余数0
3/2 = 1 余数1
十进制化成二进制举例
十进制化成八进制举例
十进制化成十六进制举例
总结:十进制转r进制
方法:除r取余,直至商0,余数倒序排列
#include <stdio.h>
int main()
{
int i = 1000;
printf("%#X\n",i);
return 0;
}
r进制转化为十进制
二进制与十六进制的转化
方法: 从右向左,四位一段,分别转化,不够四位的补零
十六进制到二进制的转化
方法: 将每一个十六进制位转化为四个二进制位
二进制与八进制相互转化
二进制到八进制
方法: 从右向左,三位一段,分别转化,不够三位的补零
八进制到二进制 方法: 将每一个八进制位转化为三个二进制位
十六进制与八进制相互转化
不存在十六进制与八进制的直接相互转化,都是以二进制为中间进制来进行转化的。
168、补码 上
在Vc++6.0中一个int类型的变量所能存储的数字的范围是多少
最小负数的二进制代码是多少
最大正数的二进制代码是多少
已知一个整数的二进制代码求出原始的数字
数字超过最大整数会怎样
码原:也叫符号-绝对值码,最高位0表示正,1表示负,其余二进制位是该数字的绝对值的二进制位。原码简单易懂,但加减运算复杂,存在加减乘除四种,增加了CPU的复杂度,零的表示不唯一。
反码:反码运算不便,也没有在计算机中应用
移码: 移码表示数值平移n位,n称为移码量,移码主要用于浮点数的阶码的存储。
补码:
十进制转二进制
正整数转二进制
除r取余,直至商0,余数倒序排列
负整数转二进制
先求出与该负数相对应的正整数的补码,然后将所有位取反,末尾+1,不够位数
时,左边补0。
#include <stdio.h>
int main()
{
int i = -100;
printf("%#X\n",i);
return 0;
}
0转二进制
全是0。
二进制转十进制
如果首位是0,则表明是正整数,按普通方法来求。
如果首位是1,则表明是负数,将所有位取反,末位加1,所的数字就是该负数的绝对值。
#include <stdio.h>
int main()
{
int i = 0xFFFFFFCA;
printf("%d\n",i);
return 0;
}
如果全是0,则对应的十进制数就是0。
169、复习上节课补码知识
#include <stdio.h>
int main()
{
int i = -5;
printf("%#X\n",i);
return 0;
}
170、补码下
在Vc++6.0中,一个int类型的变量所能存储的数字的范围是多少?
int类型变量所能存储的最大整数用十六进制表示是:7FFFFFFF
int类型变量所能存储的绝对值最大的负整数用十六进制表示是:80000000
8位二进制所代表的十进制示意图
二进制 | 十进制 |
0000 0000 | 0 |
0000 0001 | 1 |
0111 1111 | |
....... | ...... |
1000 0000 | 127 |
1000 0001 | 取反+1 1000 0000 128 -128 |
1000 0010 | -127 |
...... | ....... |
1111 1111 | -1 |
#include <stdio.h>
int main()
{
char ch = 0x80;
printf("%d\n",ch);
return 0;
}
171、链表 上
算法:
通俗定义:解题的方法和步骤。
狭义定义:对存储数据的操作。
广义定义:广义的算法也叫泛型;
无论数据是如何存储的,对该数据的操作都是一样的。
我们至少可以通过两种结构来存储数据
数组
优点:存取速度快
缺点:需要一个连续的很大的内存
插入和删除元素的效率很低
链表
专业术语:
头结点:头结点的数据类型和首节点的类型是一模一样的;
头结点是首节点前面的那个节点;
头结点并不存放有效数据;
设置头结点的目的是为了方便对链表的操作。
首节点:存放第一个有效数据的节点。
尾节点:存放最后一个有效数据的节点。
头指针:存放头结点地址的指针变量。
优点:插入、删除元素效率高
缺点:查找某个位置的元素效率低
确定一个链表需要一个参数
#include <stdio.h>
//定义可一个链表节点的数据类型
struct Node
{
int data;
struct Node*pNext;
};
int main()
{
struct Node* pHead;//pHead用来存放链表头结点的地址
pHead = CreatList();
TeaverseList(pHead);
return 0;
}
171、链表 下
#include <stdio.h>
//定义可一个链表节点的数据类型
struct Node
{
int data;
struct Node*pNext;
};
int main()
{
struct Node* pHead;//pHead用来存放链表头结点的地址
pHead = CreatList();//CreatList()功能:创建一个非循环单链表,并将头结点地址返回
teaverse_List(pHead);
return 0;
}
struct Node*creat_list(void)
{
int len;//用来存放有效结点的个数
int i;
int val;//用来临时存放用户输入的结点的值
//分配了一个不存放有效数据的头结点
struct Node*pHead = (struct Node*)molloc(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
if(Null = pNew)
{
printf("分配失败,程序终止!\n");
exit(-1);
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = Null;
pTail = pNew;
}
return pHead;
}
bool empty_list(struct Node*pHead)
{
if(pHead->pNext)//pHead->pNext == (*pHead).pNext
return true;
else
return false;
}
void traverse_list(struct Node*pHead)
{
/*
if(empty_list(pHead))
{
printf("链表为空");
}
*/
struct Node*p = pHead->pNext;
while(Null != p)
{
printf("%d\n",p->data);
p = p->pNext;
}
return;
}
174、狭义的算法
对存储数据的操作。
对不同的存储结构,要完成某一个功能所执行的操作是不一样的。
比如:
要输出数组中所有的元素的操作和要输出链表中所有元素的操作肯定是不一样的。
这说明:
算法是依附于存储结构的,不同的存储结构所执行的操作都是一样的。
175、广义的算法
176、位运算符
& 按位与 | && 逻辑与,也叫且 |
| 按位或 | || 逻辑或 |
~ 按位取反 | |
^ 按位异或(相同为0) | |
<< 按位左移 i<<3表示把i的所有二进制位左移3位,右边补0 左移n位相当于乘以2的n次方 | |
#include <stdio.h>
int main()
{
int i = 5;
int j = 7;
int k;
k = i & j;
printf("%d\n",k);
k = i && j;//k的值只能是1或0,因为&&是逻辑运算符,
//逻辑运算符的结果只能是真或假,在C中真用1表示,假用0表示
printf("%d\n",k);
return 0;
}
位运算的现实意义
通过位运算符我们可以对数据的操作精确到每一位