五、数组
一维数组
1、定义
【存储类型】 数据类型 标识符 【下标】
特点:在内存中连续存放
理解:就是一个变量,连续存放
2、初始化
不初始化
全部初始化
部分初始化
static
3、元素引用
数组名 【下标】
4、数组名
数组名是表示地址的常量
5、数组越界
数组越界不检查
经典🌰
fibonacci斐波那契数列:第n项等于前两项之和(n>2)
//求fibonacci数列的前n项,并在数组中逆序排列
static void fibonacci(void)
{
int i;
int j,tmp;
int fib[N] = {1,1};
for(i = 2; i < N ;i ++) //求出每一项,每一项等于前两项之和 ,并打印
fib[i] = fib[i-1] + fib[i-2];
for(i = 0; i < N ;i ++)
printf("%d ",fib[i]);
printf("\n");
}
冒泡排序:从第一位开始,第一位和第二位比较,大则交换,然后第二位和第三位比较,大则交换,依次相邻两位进行比较,一次之后最后一位数值最大;
重复上述步骤,进行N-1次比较后实现从小到大排序 。 每次只判断一个数,比大小,朝前或朝后移动(小数慢慢浮起来,大数沉下去慢慢)
#include<stdio.h>
#include<stdlib.h>
#define N 10
static void sort1(void)
{
int i,j,tmp;
int a[N]={45,48,24,46,68,48,24,33,1,2};
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
for(i=0;i<(N);i++) //冒泡排序关键步骤
{
for(j=0;j<(N-i);j++)
{
if(a[j]>a[j+1])
{
tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
}
}
}
for (i=0;i<N;i++) //输出
printf("%d ",a[i]);
printf("\n");
}
int main()
{
sort1();
}
选择法排序:将第一位与后面所有数值比较,比第一位小则交换位置; 第一位不动,第二位与后面其他值比较,比第二位小则交换位置以此类推,进行N-1次排序,实现N个数从小到大排序
static void sort2(void)
{
int a[N] = {23,45,90,76,13,55,76,45,3,8};
int i,j,k,tmp;
for(i = 0; i < sizeof(a)/sizeof(a[i]) ; i++)
printf("%d ",a[i]);
printf("\n");
for(i = 0 ; i < N-1 ; i++) //外循环,控制每一趟,共N-1趟
{
k=i;
for(j = i+1; j < N ; j++) //内循环,控制每一趟中的第几位进行比较
{
if(a[j] < a[k])
k = j;
}
if(i != k)
{
tmp = a[i];
a[i] = a[k];
a[k] = tmp;
}
}
for(i = 0; i < sizeof(a)/sizeof(a[i]) ; i++)
printf("%d ",a[i]);
printf("\n");
}
二维数组
定义:【存储类型】 数据类型 标识符 【行下标】 【列下标】
数组名即为数组起始位置
🌰:int M[2] [3] 两行三列的数组
在存储空间按行存放如:M[0] [1] M[0] [2] M[1] [0] M[ ] [1] M[0] [1]
部分数组初始化:int a[3] [3]={{1,2}{5,6}}
全部数组初始化:int a[3] [3]={{1,2,3},{4,5,6},{7,8,9}}
定义可以行省略:例如:int a[][N]={4,8,45,65,8}
存储形式:在一块空间连续存储
深入理解二维数组:
二维数组,本质上是多个一维数组组成的
主要是理解行指针和列指针
字符数组
1、定义 , 初始化 ,存储特点
【存储类型】 数据类型 标识符 【下标】
单个字符初始化
用字符串常量初始化
2、输入输出
scanf("%s",str);
printf("%s",str);
注意%s无法获得带分隔符的字符串
🌰
输入 hello word
输出为 hello
输入可以改为 scanf(“%s%s”,str) 来解决这个问题;
3、常用函数
因为int等类型可以用if来比大小,也可以相互赋值,但是字符串的名字就是一个地址常量,没办法比较大小,赋值等操作,所以需要常用函数来解决这些问题
不能直接复制 例如: str=“abcde”,这个是不成立的
(1)strlen
作用:以尾零作为结束,显示当前字符串个数 (输入Hello word,返回值为5)
printf("%d\n",strlen(str))
(2)sizeof
作用:该字符串在内存所占字节个数 (输入Hello word,返回值为11)
printf("%d\n",sizeof(str));
(3)strcpy
用法strcpy(str,"abcde"); 作用:将abcde字符串copy到str中
puts(str);
(4)strncpy
用法:strncpy(str,“abcde”,STRSIZE);
作用:将abcde字符串copy到str中,但是STRSIZE会限制copy个数,防止溢出
(5)stract
char *strcat(char *dest, const char *src);
作用:将两个字符串首尾连接
(6)strncmp
int strcmp(const char *s1, const char *s2);
作用:比较ASCLL码大小,根据比较大小情况,返回值为1,0,-1
经典代码
//编写一个字符串拷贝
#include <stdio.h>
#include <stdlib.h>
char *mystrcpy(char *dest,const char *src)
{
char *ret = dest;
if(dest != NULL && src != NULL)
while((*dest++ = *src++) != '\0')
return ret;
}
char *mystrncpy(char *dest, const char *src, size_t n)
{
int i;
for(i = 0; i < n && (dest[i] = src[i]) ;i++);
;
dest[i] = '\0';
return dest;
}
int main()
{
char str1[] = "helloworld";
char str2[128];
mystrcpy(str2,str1);
//mystrncpy(str2,str1,5);
puts(str2);
exit(0);
}
`
]) ;i++);
;
dest[i] = '\0';
return dest;
}
int main()
{
char str1[] = "helloworld";
char str2[128];
mystrcpy(str2,str1);
//mystrncpy(str2,str1,5);
puts(str2);
exit(0);
}
六、指针
1、变量与地址
变量: 抽象出来的某一块空间的命名,能变,就是能读能写,必定在内存里
地址: 即指针,即指针保存的是地址
🌰
int i = 1;
int *p = &i;
int **q = *p;
--->
i = 1
&i = 0x2000
p = 0x2000 = &i --> p = &i;
&p = 0x3000
*p = *(0x2000) = *(&i) = i --> *p = i;
q = 0x3000 = &p
&q = 0x4000
*q = *(0x3000) = *(&p) = p --> *q = p = &i;
**q = *p = *(&i) = i --> **q = i
理解上述i, *p, **p 之间的关系,指针就没多大问题了
访问 i 的值:可以通过 i, *p, **q
访问 i 的地址:可以通过 &i, p, *q
2、指针与指针变量
*定义:[数据类型] p=&i;
🌰
int *p = &i ;相当于:定义一个指针类型的p,然后把i的地址 赋给p;
int *p;
p = &i;
i 的值等价于: i , *p, **q
i 的地址等价于:&i , p , *q
指针:
指针变量
无论是什么类型的指针,在64位处理器中所占字节数 sizeof§ ,所占字节均为8
🌰
int *p;
char *p;
doubule *p;;
struct ×× *p; //结构体指针
无论是什么类型的指针,sizeof(*p) 在64位所占字节数 均为8
虽然指针所占字节,指针变量和他所指类型的值,一定要相符
🌰
#include <stdio.h>
#include<stdlib.h>
int main()
{
int i=1;
int *p;
p=&i;
printf("i=%d\n",i); //输出i的值
printf("&i=%p\n",&i); //i的地址
printf("p=%p\n",p ); //p的值,存放的是i的地址
printf("&p=%p\n",&p); // p的地址,一块独立的指针空间
printf("*p=%p\n",*p); //p所指的i中的内容
exit(0);
}
理解一级指针与二级指针关系:
#include <stdio.h>
int main()
{
int i = 1;
int *p = &i; //可以看作是int* p, 定义一个指针类型的一级指针p,然后把i的地址赋给p
int **q = &p; //可以看作是int** q, 定义一个指针类型的二级指针q,然后把p的地址赋给q
printf("sizeof(i) = %d\n",sizeof(i));
printf("sizeof(p) = %d\n",sizeof(p));
printf("i = %d\n",i); // i = 1
printf("&i = %p\n",&i); // &i = 000000000061FE1C
printf("p = %p\n",p); // p = 000000000061FE1C
printf("&p = %p\n",&p); // &p = 000000000061FE10
printf("*p = %d\n",*p); // *p = 1
printf("q = %p\n",q); // q = 000000000061FE10
printf("&q = %p\n",&q); // &q = 000000000061FE08
printf("**q = %d\n",**q); // **q = 1
return 0;
}
输出:
sizeof(i) = 4
sizeof(p) = 8
i = 1
&i = 000000000061FE1C
p = 000000000061FE1C
&p = 000000000061FE10
*p = 1
q = 000000000061FE10
&q = 000000000061FE08
**q = 1
3、直接访问与间接访问
直接访问:直接访问变量的值或地址
间接访问:通过其他变量访问
4、空指针与野指针
指针定义:int *p =NULL
(指针定义为NULL,是防止野指针的产生,如果你不清楚指向谁,就定义指针为NULL)
野指针是非常危险的,一定要让指针有去处
5、空类型
void *q=NULL (void类型的指针是一个万金油,它与任何指针类型都可以相互赋值)
6、定义与初始化的书写规则
int i=1:
int *p=&i
int **q =&i
7、指针运算
&取地址 *取值 关系运算 ++ –
8、指针与数组
指针与一维数组
指针与二维数组
指针与字符数组
字符指针:
字符数组:
9、const 与指针
用const修饰一个变量(定义时并赋值),表示将这个变量常量化,其数值保持不变
const int a;
int const a;
常量指针:
指向常量的指针,即p的指向的内存可以变,P指向的数值内容不可变
const int *p;
*int const p ; //先const后p(记忆方法)
const修饰的是p,说明*p不能变,但是p的指向可以变,可以通过其他定义其他来改变这个变量的值
🌰
int main()
{
int i = 1;
int j = 100;
const int *p = &i;
i = 10; //正确:i的值可以变
*p = 10;//错误:const 修饰*p ,*p不能改变
p = &j;//正确:指针的指向可以改变
}
指针常量:
本质是一个常量,而用指针修饰它,即p的指向的内存不可以变,但是p内存位置的数值可以变
int *const p; //先int * 后const(记忆方法)
const修饰的是p,说明p的值不能变,即p的指向不能变
🌰
int main()
{
int i = 1;
int j =100;
int * const p = &i;
i = 10;//正确
p = &j;//错误:const修饰的p,p的指向不能改变
*p = 10;//正确:p的值可以改变
printf("%d\n",*p);
}
const int *const p; 是指针常量也是常量指针,即指向常量的常量指针,
即p的指向不能变,指向的目标变量的值也不能变
🌰
int main()
{
int i = 1;
int j =100;
const int * const p = &i;
p = &j;//错误:后面的const修饰的p,p的指向不能改变
*p = 10;//错误:前面const修饰*p,*p的值也不可以改变
printf("%d\n",*p);
}
10、指针数组与数组指针
数组指针:表示的是一个指针,该指针指向的是一个数组
【存储类型】 数据类型 (*指针名) 【下标】 = 值
🌰:int (*p)[3];
-> type name ;
-> int[3] *p ;定义了一个指针变量,指向了一个有3个int型元素的数组,即指向int[3]
指针数组:表示的是一个数组,而数组中的每一个元素都是指针
【存储类型】 数据类型 * 数组名【长度】
🌰: int * arr[3];
-> type name ;
-> int *[3] arr ;定义了一个数组名为arr,数组中3个元素,每一个元素都是int *的指针,即int *[3],
11、多级指针
七、函数
1、函数的定义
数据类型 函数名 (【形式参数说明表】)
(【数据类型 形参名,数据类型 形参名】)
int main(int argc, char *argv[])
注:int 表示返回的数据类型
**argc** 是 argument count的缩写,表示传入main函数的参数个数;
**argv** 是 argument vector的缩写,表示传入main函数的参数序列或指针,并且第一 个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,所 以确切的说需要我们输入的main函数的参数个数应该是argc-1个;
2、函数的传参
值传递
//函数传参:值传参、
#include <stdio.h>
int swap_value(int a,int b) //值传参:将main主函数中的参数以数值的形式传给被调用的函数
{
int tmp;
tmp = a;
a = b;
b= tmp;
return 0;
}
int main()
{
int i =3,j =5;
swap_value(i,j); //值传参
printf("i = %d\nj = %d\n",i,j);
return 0;
}
输出:i=3,j=5
并没有起到交换i与j的作用,如上图,只是新开辟出一块地址,进行了交换,仅i,j名字是一样的,但根本不是一回事
地址传参
//函数传参:地址传参
#include <stdio.h>
void swap_address(int *p,int *q) //地址传参:地址传参数要以指针类型来接收
{
int tmp;
tmp = *p;
*p = *q;
*q = tmp;
}
int main()
{
int i =3,j =5;
swap_address(&i,&j); //地址传参
printf("i = %d\nj = %d\n",i,j);
return 0;
}
输出:i=5,j=3
上面这个函数达到了交换数的目的,通过另外一个函数达到修改其他函数的目的,一般用地址传递
全局变量
3、函数的调用
函数调用
递归:一个函数,直接或间接调用自身
/*递归调用,实现斐波那契数列
1,1,2,3,5,8,13,21,34.....
n=(n-1) + (n-2)
n-1 = (n-1-1) + (n-1-2)
n-2 = (n-2-1) + (n-2-2)
第一项 1=1
第二项 2=1
用法:输入n,(n表示第n项),输出斐波那契数列第n项
*/
#include <stdio.h>
int fib(int n)
{
if(n < 1)
return -1;
if(n == 1 || n == 2)
return 1;
return fib(n-1) + fib(n-2);//关键步骤:递归调用
}
int main()
{
int n;
int res;
scanf("%d",&n);
res = fib(n);
printf("fib[%d]= %d\n",n,res);
return 0;
}
4、函数与数组
函数与一维数组
模块化函数与一维数组之间如何传参,如何定义接收的数组的类型,然后在被调用函数中输出
#include <stdio.h>
#include <stdlib.h>
void print_arr(int *p, int n) // main函数传一个数组过来可以用int *p接收
{
int i;
printf("%s:%d\n",__FUNCTION__,sizeof(p)); //指针的大小,在64位处理器均为8个字节
for(i = 0;i < n; i++)
printf("%d ",*(p+i));
printf("\n");
}
void print_arr2(int p[],int n) //也可以用int p[],因为在表示形参时,一个[]相当于*
{
int i;
for(i =0; i< n; i++)
printf("%d ",p[i]);
printf("\n");
}
int main(int agrc, char **argv) //第二个形参也可以表示为char *argc[]
{
int a[] = {1,3,5,7,9};
printf("%s:%d\n",__FUNCTION__, sizeof(a)); //数组的大小
print_arr(a,sizeof(a)/sizeof(*a));
}
输出:
main:20
print_arr:8
1 3 5 7 9
函数与二维数组
模块化函数与二维数组之间如何传参,如何定义接收的数组的类型,然后在被调用函数中输出
#include <stdio.h>
#include <stdlib.h>
#define M 3
#define N 4
void print_arr(int *p,int n) //将一个二维数组看成一个大的一维数组
{
int i,j;
printf("sizeof(p) = %d\n",sizeof(p));
for(i =0;i < n; i++)
printf("%d ",p[i]);
printf("\n");
}
void print_arr1(int (*p)[N], int m,int n) //定义一个数组指针,维数为列,区分行和列
{
int i,j;
printf("sizeof(p) = %d\n",sizeof(p));//8
for(i = 0;j < m; j++)
{
for(j =0 ;j < n;j++)
printf("%4d ",*(*(p+i)+j));
printf("\n");
}
}
float average_score(int *p,int n) //求平均值
{
float sum = 0;
int i;
for(i < 0;i < n; i++)
sum += p[i];
return sum/n;
}
//指针函数:指的是某一个函数的返回值为一个指针
int * find_num(int (*p)[N],int num)
{
if(num > M-1) //当传过来的num值大于行数时,整形类型返回值是-1,
return NULL; //指针类型返回值是空NULL
return *(p+num);
}
int main()
{
int i,j;
int a[M][N] = {1,2,3,4,5,6,7,8,9,10,11,12};
float ave;
int num = 0 ;
int *res;
#if 0 //第一种 :把三行四列的二维数组当成一个大的有12个元素的一维数组
print_arr(&a[0][0],M*N); //传二维数组的首地址,再传共有多少个数据
// 也可以是a*,即把二维数组当作一个大的一维数组
//*a == *a(a+0) == a[0] 这种做法都是将行指针转化为列指针
#endif
#if 1 //第二种:有区分行和列,
print_arr1(a,M,N); //a的本质是一个指向数组的指针,即数组指针
printf("sizeof(a) = %d",sizeof(a)); //48
#endif
#if 0
ave = average_score(*a,M); //求平均值
printf("ave = %f\n",ave);
#endif
#if 0
//找某一行的数据并 输出
find_num(a,num);//传行指针
#endif
#if 0
//用指针函数,返回值为指针,需要定义一个指针来接收
res = find_num(a,num);
if(res != NULL)
{
for(i = 0; i < N; i++)
printf("%d ",res[i]);
printf("\n");
}
#endif
exit(0);
}
5、函数与指针
指针函数(是一个函数,函数的返回值是指针)
返回值 * 函数名 (形参)
int * fun(int);
int main()
{
int i,j;
}
函数指针 (指的是一个指针,指针的指向是函数)
类型 (*指针)(形参);
🌰
int (*p)(int); //定义一个指针,该指针指向一个函数
int *p;//定义一个指针,该指针指向一个int整型
float *p;//定义一个指针,该指针指向一个float浮点型
通过函数理解函数指针
函数:
int add(int a,int b)
{}
int main()
{
int ret;
ret = add(a,b);
}
函数指针,即定义一个指针,该指针指向函数
int main:()
{
int ret;
int (*p)(int,int);//本质为定义一个int (int,int) *p,
//即定义一个指针,该指针指向一个函数
p = add; //add也可以加& -> p = &add;
ret = p(a,b); //也可以 ret = *p(a,b);
}
🌰
//函数指针
#include <stdio.h>
#include <stdlib.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
if(a > b)
return a - b;
else
return b - a;
}
int main()
{
int a =3,b =5;
int ret;
int i;
//函数指针
int (*p)(int,int);
int (*q)(int,int);
p = add;
q = sub;
// ret = add(a,b);
ret = p(a,b);
printf("%d\n",ret);
ret = q(a,b);
printf("%d\n",ret);
exit(0);
}
函数指针数组
类型 (*数组名【下标】) (形参);
🌰
int (*arr[N])(int);
int (int,int) *arr[N] -->定义一个函数指针数组,
这是一个数组,数组中每一个元素都是指针,每个指针都指向函数
即
int (*arr[N])(int,int);
arr[0] = 函数1;
arr[1] = 函数2;
…………
//函数指针
#include <stdio.h>
#include <stdlib.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
if(a > b)
return a - b;
else
return b - a;
}
int main()
{
int a =3,b =5;
int ret;
int i;
int (*funcp[2])(int,int); //函数指针数组
funcp[0] = add;
funcp[1] = sub;
for(i = 0;i < 2;i++)
{
ret = funcp[i](a,b);
printf("%d\n",ret);
}
exit(0);
}
指向指针函数的函数指针数组
int *(*funcp[N])(int)
八、构造类型
结构体
1、产生及意义
数组只能存储一种类型,结构体可以存储多种类型
2、类型描述
struct 结构体名 // 命名习惯后加-st
{
数据类型 成员1 ;
数据类型 成员2 ;
…
};
🌰1:
struct student_st //定义结构体类型
{
int id;
char name[128];
int math;
int chinese;
};
struct student_st stu1,stu2; //声明两个结构体类型的变量stu1,stu2
🌰2:
struct student_st
{
int id;
char name[128];
int math;
int chinese;
}stu1,stu2; //定义类型的同时声明变量
🌰3:
struct
{
int id;
char name[128];
int math;
int chinese;
}stu1,stu2; //直接声明变量
3、嵌套定义
4、定义变量(变量、数组、指针),初始化及成员引用
结构体数组:
🌰
struct student_st //定义结构体类型
{
int id;
char name[128];
int math;
int chinese;
};
struct student_st stu[10];//声明一个结构体类型的数组
🌰
struct student_st //定义结构体类型
{
int id;
char name[128];
int math;
int chinese;
}stu[N]; 定义一个结构体类型同时声明一个数组变量
结构体指针:
定义:struct 结构体类型名 * 指针变量名
成员引用:
变量名方式: 变量名.成员名 //变量名指的是结构体的名字
指针方式: 指针 ->成员名 //指针指的是结构体指针的名字
或 (指针).成员名 //小括号不能少,因为.的优先级比要高
🌰
struct student_st
{
int id;
char name[128];
int math;
int chinese;
}stu1;
main:
通过变量名访问: stu1.id
通过指针访问:
struct student_st *pstu; //定义一个结构体指针变量*pstu,并使*pstu指向结构体变量stu1
pstu->id;
pstu->name;
(*p).id;
结构体指针数组:
struct student_st
{
int id;
char name[128];
int math;
int chinese;
}stu1[5]; //定义一个结构体类型并声明一个数组变量
main:
struct student_st *p;
p = stu1; //把结构体数组stu1的首地址赋给指针变量p后,p就指向了结构体数组的第0个元素stu1[0]
p++; //可使结构体指针依次指向结构体数组的各个元素
结构体 和 函数:
函数的参数类型是结构体/结构体指针:
struct student_st //定义结构体类型
{
int id;
char name[128];
int math;
int chinese;
};
🌰//被调用函数,形参data是 结构体变量
int func(struct student_st data)
{}
int main()
{
struct student_st stu1;
func(stu1); //直接传结构体的值--->值传参
}
🌰//被调用函数,形参pdata是 结构体指针变量
int func1(struct student_st *pdata)
{}
int main()
{
struct student_st stu1;
func1(&stu1); //传的是结构体的地址-->地址传参
}
函数返回结构体类型的值/结构体指针 ------> 结构体类型的函数:
struct 结构体类型名 函数名(形参)
{
函数体
}
🌰
struct student_st //定义结构体类型
{
int id;
char name[128];
int math;
int chinese;
};
struct student_st func2(struct student_st data) //定义一个结构体类型的函数,且形参也是结构体
{
struct student_st tmp; //定义一个结构体类型的参数
……;
return tmp; //返回值的类型是结构体
}
int main()
{
struct student_st res,stu2;
res = func2(stu2); //用结构体类型来接收返回值
}
5、占用内存空间大小
6、函数传参(值传参,地址传参)
共用体
1、产生及意义
2、类型描述
union 共用体名
{
数据类型 成员名1;
数据类型 成员名2;
}
3、嵌套定义
4、定义变量(变量,数组,指针),初始化及成员引用
5、占用内存空间大小
6、函数传参(值传参,地址传参)
7、位域
大端小端
大端:数据的低位保存在高地址中 C51
小端:数据的低位保存在低地址中
枚举
enum 标识符
{
成员1,
成员2, //用逗号分隔,最后一个不用逗号
……
}; //和结构体一样记得加分号