C语言3大难点:数组、指针、函数
数组
多个同种数据类型数据的集合,在内存空间上连续存储
基本形式:
<存储类型> <数据类型> <数组名>[元素个数]
存储类型:数组存储的方式
数据类型:存储的多个元素是什么数据类型,就写什么数据类型
数组名:和标识符的命名规则保持一致
是数组的首个元素的地址,代表了整个数组
元素个数:数组想要存放多少个元素,这里就写多少;写的是一个常量(普通整型常量:10,20,5;标识符常量:#denfine SIZE 20)
声明和定义:
int arr[5]; char buf[10];
初始化:
1、完全初始化
数组的每一个元素,都赋予一个初始值 例: int arr[5] = {0,1,2,3,4}; char buf[5] = {'h','e','l','l','o'};
2、部分初始化
数组的前面部分元素给予一个初值 例: int arr[5] = {0,1,2}; char buf[5] = {'w','o'}; 未被赋值的元素默认为0(字符数组默认为'\0') int arr[5] = {0};//全部为0 char buf[5] = {0};//全部为'\0'
3、未初始化
定义时未给元素赋初值 例: int arr[5]; char buf[5]; 数组中的元素为随机值
4、省略初始化
定义时未给定元素个数 例: int a[] = {1,2,3}; 初始化时给定多少个元素,系统就分配多大的空间
数组的使用:
通过数组名加下标的方式去访问
int a[5]; 该数组能访问到的元素:a[0] a[1] a[2] a[3] a[4] 若一个数组的元素个数为n 那该数组能访问到的下标为0~n-1 若访问a[5]则数组越界访问(非法访问内存),会产生段错误 定义数组时,下标表示元素个数 在使用数组时,下标表示的是第几个元素 注意:定义和使用时,下标的含义不一样
数组的遍历
#include <stdio.h>
int main(int argc, char *argv[])
{
int arr[5] = {0, 1, 2, 3, 4};
char buf[10];
int i = 0;
for( i = 0; i < 5; i++)
{
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
结果: arr[0] = 0 arr[1] = 1 arr[2] = 2 arr[3] = 3 arr[4] = 4
练习:
定义一个整型数组,计算所有元素的和,并打印其中的最大值和最小值及其下标
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[10] = {7,81,3,99,102,24,17,1,82,56};
int sum = 0;
int i = 0;
int max = a[0];
int min = a[0];
int max_i = 0;
int min_i = 0;
for( i = 0; i < 10; i++)
{
if( a[i] >= max)
{
max = a[i];
max_i = i;
}
if( a[i] <= min)
{
min = a[i];
min_i = i;
}
sum += a[i];
}
printf("sum:%d\n", sum);
printf("max:%d max_i:%d\n", max, max_i);
printf("min:%d min_i:%d\n", min, min_i);
return 0;
}
结果: sum:472 max:102 max_i:4 min:1 min_i:7
字符数组和字符串
字符数组可以用来存放字符串
char buf[6] = {'h','e','l','l','o'}; char buf[6] = "hello"; char buf[5] = {'h','e','l','l','o'}; char buf[5] = "hello"//数组越界访问 注意:用字符数组存放字符串时,一定要给'\0'预留空间
练习:从键盘上输入一个字符串,求该字符串的有效长度
#include <stdio.h>
int main(int argc, char *argv[])
{
char buf[10] = {0};
gets(buf);
int i = 0;
while(buf[i] != '\0')
{
i++;
}
/*
for(i = 0; i < 10; i++)
{
if( buf[i] == '\0')
{
printf("i:%d\n", i);
break;
}
}
*/
printf("i:%d\n", i);
return 0;
}
练习:从键盘上获取两个字符串,分别存放在两个数组里面,比较两个数组是否相同,若相同,输出0,若第一个比第二个大,则输出1,如果第一个比第二个小,则输出-1;
#include <stdio.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
gets(a);
gets(b);
int i = 0;
for( i = 0; i < 10; i++)
{
if( a[i] > b[i])
{
printf("1\n");
break;
}
else if( a[i] < b[i])
{
printf("-1\n");
break;
}
else
{
if( 9 == i)
printf("0\n");
continue;
}
}
return 0;
}
数组的大小
sizeof(数组名):计算数组的字节大小 int arr[10] = {0}; sizeof(arr) == 40;(4*10)即 数据类型所占字节数*数组的元素个数 sizeof(数组名)/sizeof(数组的数据类型) -->元素个数 sizeof(数组名)/元素个数 -->数组的数据类型
字符串函数
1、strlen
头文件:#include <string.h> 函数原型:size_t strlen(const char *s); 作用:求字符串有效长度 例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[10] = {0};
int arr[10] = {0};
scanf("%s", buf);
printf("sizeof(buf):%ld\n", sizeof(buf));
printf("strlen:%ld\n", strlen(buf));
printf("siezof(arr):%ld\n", sizeof(arr));
return 0;
}
结果: asdf sizeof(buf):10 strlen:4 siezof(arr):40
2、strcmp
头文件:#include <string.h> 函数原型:int strcmp(const char *s1, const char *s2); 返回值: 如果两个字符串相等,返回值为0 如果前一个字符串比后一个大,返回值为不同的那两个字符之间的差值,并且为正数 如果前一个字符串比后一个小,返回值为不同的那两个字符之间的差值,并且为负数 比较是一个一个字符从头开始比较,若相同则比较下一个字符,直到比较完所有的字符 例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
gets(a);
gets(b);
printf("strcmp:%d\n", strcmp(a,b));
return 0;
}
结果: asd asd strcmp:0 aad asd strcmp:-18 aad asd strcmp:-18 abd asd strcmp:-17 abd acd strcmp:-1 acd abd strcmp:1 asd aad strcmp:18
3、strcat
头文件:#include <string.h> 函数原型:char *strcat(char *dest, const char *src); 作用将后面一个字符串,粘贴到前面一个字符串的后头 例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
gets(a);
gets(b);
strcat(a,b);
puts(a);
puts(b);
return 0;
}
结果: asd bnm asdbnm bnm
4、strcpy
头文件:#include <string.h> 函数原型:char *strcpy(char *dest, const char *src); 例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
gets(a);
gets(b);
strcpy(a,b);
puts(a);
puts(b);
printf("a[4]:%c\n", a[4]);
return 0;
}
结果: asdzxcv bnm bnm bnm a[4]:x 结论:该函数并不是完完全全的复制,而是将后一个字符串复制一份出来压到前一个字符串里面
作业:不用字符串函数,实现strcat和strcpy
#include <stdio.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
gets(a);
gets(b);
int i = 0;
for(i = 0; i < 10; i++)
{
a[i] = b[i];
if( b[i] == '\0')
{
break;
}
}
puts(a);
puts(b);
return 0;
}
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
gets(a);
gets(b);
int len = strlen(a);
for(int i = 0; i < 10;i++)
{
a[len] = b[i];
len++;
if( b[i] == '\0')
break;
}
puts(a);
puts(b);
return 0;
}
从键盘输入一个数, 计算这个数 补码 中 1 的个数
#include <stdio.h>
int main(int argc, char *argv[])
{
unsigned int a = 0;
int temp = 0;
scanf("%d", &a);
while( a != 0)
{
if( a % 2 == 1)
temp++;
a /= 2;
}
printf("temp:%d\n", temp);
return 0;
}
利用 * 打印一个菱形
#include <stdio.h>
int main(int argc, char *argv[])
{
int n = 0;
scanf("%d", &n);
int i,j;
for( i = 0; i < n/2+1;i++)
{
for(j = 0;j < n/2 - i;j++)
{
printf(" ");
}
for(j = 0;j < 2*i + 1;j++)
{
printf("*");
}
putchar('\n');
}
for( i = 0; i < n/2;i++)
{
for(j=0;j<=i;j++)
{
printf(" ");
}
for(j=0;j<n-2-2*i;j++)
{
printf("*");
}
putchar('\n');
}
return 0;
}
冒泡排序
#include <stdio.h>
int main(int argc, char *argv[])
{
int i = 0;
int j = 0;
int a[10] = { 3, 7, 76, 98, 4, 23, 54, 67, 31, 2};
int temp = 0;
for(j = 0; j < 10; j++)
{
for( i = 0; i < 9; i++)
{
if( a[i] > a[i+1])
{
temp = a[i];
a[i] = a[i+1];
a[i+1] = temp;
}
}
}
for( i = 0; i < 10; i++)
{
printf("a[%d]:%d ",i, a[i]);
}
putchar('\n');
return 0;
}
二维数组
多个同种类型一维数组的集合
一般形式:
<存储类型> <数据类型> <数组名>[下标][下标] <存储类型> <数据类型> <数组名>[行数][列数] 例: int a[2][3]; 数组名: a:是这个二维数组首行的地址,代表了整个二维数组 a[0]:是二维数组中首个一维数组的首元素地址,代表了首个一维数组
定义和声明
int a[2][3]; char b[2][3];
数组元素的使用
数组名[下标][下标] 如果数组的第一个下标为n,第二个下标为m 最多能访问到a[n-1][m-1]; 最小为a[0][0];
二维数组的初始化
1、完全初始化
include <stdio.h>
int main(int argc, char *argv[])
{
int a[2][3] = {{1,2,3},{4,5,6}};
int b[2][3] = {1,2,3,4,5,6};
char c[2][3] = {{"he"},{"wa"}};
char buf[2][3] = {"he","wa"};
//char d[2][3] = {"hello"};
printf("%c\n", buf[0][0]);
return 0;
}
2、部分初始化
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[2][3] = {{1,2},{3}};
int b[2][3] = {1,2,3};
char c[2][3] = {'h','e','l'};
char d[2][3] = {"h","el"};
return 0;
}
3、未初始化
数组中的元素为随机值
4、省略初始化
第一个下标可以省略(即行号可以省略,列号不行) int a[][3] = {1,2,3,4};
二维数组的遍历
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[2][3];
int i = 0;
int j = 0;
for(j = 0; j < 2;j++)
{
for( i = 0; i < 3; i++)
{
printf("a[%d][%d]:%d\n", j, i, a[j][i]);
}
}
return 0;
}
练习:
定义一个二维数组,往里面放五个国家的名字,然后进行一个从小到大的排序;
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[5][10] ={"China","German","Franch","Japan","England"};
/*
char a[10] = "asd";
char b[10] = "bnc";
char c[10] = {0};
int i = 0;
for(i = 0; i < 10; i++)
{
c[i] = a[i];
a[i] = b[i];
b[i] = c[i];
}
puts(a);
puts(b);
*/
char c[10] = {0};
int i = 0;
int j = 0;
for(j = 0; j < 4; j++)
{
for( i = 0;i < 4; i++)
{
if(0 < strcmp(buf[i],buf[i+1]))
{
strcpy(c,buf[i]);
strcpy(buf[i],buf[i+1]);
strcpy(buf[i+1],c);
}
}
}
for(i = 0; i < 5; i++)
{
printf("%s\n", buf[i]);
}
return 0;
}
练习:打印杨辉三角
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[30][30] = {0};
int i = 0;
int j = 0;
int n = 0;
scanf("%d", &n);
for( j = 0; j < n; j++)
{
for( i = 0; i < j + 1;i++)
{
if( i == 0 || i == j)
{
a[j][i] = 1;
}
else
{
a[j][i] = a[j-1][i] + a[j-1][i-1];
}
}
}
for( j = 0; j < n; j++)
{
for( i = 0; i < j + 1;i++)
{
printf("%d ", a[j][i]);
}
putchar('\n');
}
return 0;
}
指针
指针就是地址,地址就是内存空间中每一个内存单元(1byte(字节))的“门牌号”
如 内存空间中第2个内存单元的地址(门牌号)就是0x2
指针变量
基本形式:
<数据类型> <*> <指针名>; 例: int *p; char *q;
作用:
用于保存地址(指针)
初始化:
例: int a = 10; char b = 'A'; int *p = &a; 如果初始化时没有需要保存的地址,则赋值为空; 例: int *p = NULL;((void *)0) int *p;//野指针 p = &a; *p = 10; int *p,m =10,n; int *p; int m =10; int n;
指针的使用:
&:后面跟变量名,表示取该变量的地址 *: 使用时,表示取该地址所对应的内存空间中的内容 定义变量时,表示该变量是一个指针变量 *和&符号互为逆运算 int a = 10; int *p = &a; *p == *(&a) == a
指针的运算:
int a = 10; int b =20; int *p = &a; int *q = &b; 指针的基础运算 加法: p + 3; p++; p - 3; p--; p + q -->无任何含义的 p - q -->计算地址的高低,两个指针之间有多少个元素 * / %无实际意义 p + n --> 指针偏移 p + n*sizeof(指向类型)
指针类型和指向类型:
指针类型:去掉指针变量的变量名之后,剩余的部分就叫指针类型 例: int *p = NULL; 它的指针类型就是:int *类型 指向类型:去掉指针变量的变量名之后,再去掉与其相邻的第一个*,剩余的部分就叫指向类型 int *p = NULL; 它的指向类型就是:int类型 int **p; int (*)p[6]; 指向类型决定了该指针变量+1跳过的字节长度,决定了*p取内容取出来的字节数
指针的大小
无论是int *类型的指针变量,还是char *类型的指针变量 在64位linux系统中恒定为8个字节大小 在32位linux系统中恒定位4个字节大小
指针变量和字符串
char *p = "hello"; p可以被修改 但*p不能被修改,因为*p表示的是一个字符串常量,常量不能被修改
指针变量与数组
int a[5] = {0,1,2,3,4}; int *p = a; *p == a[0]; *(p + 1) == a[1]; *(p + n) == a[n]; ----------------- *p == a[0]; p++; *p == a[1]; int a[5] = {0,1,2,3,4}; int *p = a; p[0] == a[0] p[1] == a[1] 偏移后 p += 2; p[0] == a[2]; *(a + 1) == *( p + 1) a与p不等价,p是一个指针变量;a是一个地址常量,是数组的首地址,不能和p一样进行自加操作
#include <stdio.h>
int main(int argc, char *argv[])
{
/*
char a[10] = "hello";
char *p = &a[4];
int i = 0;
for( i = 0; i < 5; i++)
{
printf("%c", *p);
p--;
}
putchar('\n');
puts(a);
*/
char a[10] = "hello";
char b = 0;
char *p = a;
char *q = a;
while( *(q + 1) != '\0')
{
q++;
}
int i = 0;
for( i = 0; i < 2; i++)
{
b = *p;
*p = *q;
*q = b;
p++;
q--;
}
puts(a);
p++;
printf("%c\n", *(a + 1));
return 0;
}
strlen:
#include <stdio.h>
int main(int argc, char *argv[])
{
char buf[10] = {0};
scanf("%s", buf);
char *p = buf;
int temp = 0;
while( *p != '\0')
{
temp++;
p++;
}
printf("temp:%d\n", temp);
return 0;
}
strcat:
#include <stdio.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
char *p = a;
char *q = b;
gets(a);
gets(b);
while( *p != '\0' )
{
p++;
}
while( *q != '\0')
{
*p = *q;
p++;
q++;
}
puts(a);
puts(b);
return 0;
}
strcmp:
#include <stdio.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
char *p = a;
char *q = b;
gets(a);
gets(b);
int i = 0;
for( i = 0;i < 10; i++)
{
if( *p == *q)
{
if( i == 9)
{
printf("0\n");
break;
}
p++;
q++;
}
else if( *p > *q)
{
printf("%d\n", *p - *q);
break;
}
else
{
printf("%d\n", *p - *q);
}
}
return 0;
}
strcpy:
#include <stdio.h>
int main(int argc, char *argv[])
{
char a[10] = {0};
char b[10] = {0};
char *p = a;
char *q = b;
gets(a);
gets(b);
do{
*p = *q;
p++;
}while( *(q++) != '\0');
puts(a);
puts(b);
return 0;
}
const和指针
const int *p *p不能被改变,即p所储存的地址 所对应的内存空间当中的内容 不能被改变 int const *p *p不能被改变,即p所储存的地址 所对应的内存空间当中的内容 不能被改变 int * const p p不能被改变,即p所储存的地址不能被改变
实现整型数组循环右移
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int a[6] = {1,2,3,4,5,6};
int *p = &a[5];
int cout = 0;
scanf("%d", &cout);
int i = 0;
int j = 0;
for( i = 0; i < cout; i++)
{
int temp = *p;
for(j = 5;j >=1;j--)
{
a[j] = a[j-1];
}
a[0] = temp;
}
for(i = 0;i<6;i++)
{
printf("a[%d]:%-3d",i,a[i]);
}
printf("\n");
return 0;
}
void类型指针
int a = 10; void *p = NULL; p = &a; printf("a:%d\n",*((int *)p));
指针的活用
大小端 低位的数据存放在低地址空间:小端存储 低位的数据存放在高地址空间:大端存储
#include <stdio.h>
int main(int argc, char *argv[])
{
/*
int a = 0x12345678;
char *p = &a;
p++;
short *q = p;
printf("*p:%#X\n", *q);
*/
/*
char a[6] = "hello";
int *p = a;
p++;
printf("*p:%c\n", *((char *)p));
*/
int b[2] = {10,20};
char *p = b;
p += 4;
printf("*p:%d\n", *p);
return 0;
}
指针变量和二维数组
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[2][3] = {0,1,2,3,4,5};
int *p = a[0];
int sum = 0;
int i = 0;
for(i = 0; i < 6;i++)
{
sum += *(p + i);
printf("%d\n", *(p+i));
}
printf("sum:%d\n", sum);
return 0;
}
数组指针
数组指针也叫行指针,本质是一个指针,指向了数组的某一行 基本形式: 数据类型 (*数组指针名)[该数组的列号(该二维数组每个一维数组的元素个数)] 例: int (*p)[3]; 例:
#include <stdio.h>
int main(int argc, char *argv[])
{
/*
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4] = a;
printf("%d\n",**p);
*/
/*
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int *p = a;
printf("%d\n", *p);
int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*q)[4] = b;
printf("%d\n", *(*(q+1)+1));
*/
char buf[5][10] = {"hello","world","amazing","happy","angry"};
char (*p)[10] = buf;
printf("%c\n", **p);
printf("%c\n", *(*(p+1)+1));
printf("%c\n", *(*(p+2)+2));
printf("%c\n", *(*(p+3)+3));
printf("%s\n", *(p+4));
printf("%s\n", *(p+2)+4);
printf("%c\n", *p[2]);
printf("%c\n", (*p)[2]);
return 0;
}
指针数组
本质是一个数组,用于存储指针 数据类型 *数组名[元素个数] 例:
int *a[3];
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[3] = {0,1,2};
int *p1 = &a[0];
int *p2 = &a[1];
int *p3 = &a[2];
int *b[3] = {p1,p2,p3};
printf("%d\n",*b[2]);
return 0;
}
二级指针
用来保存指针变量的地址的指针变量 int a = 10; int *p = &a; int **q = &p;
函数
封装好的代码块,可以被直接调用,相当于在让别人帮我做事情
printf() scanf() strlen() strcpy() strcat() main()
函数的基本形式: int main(int argc, char *argv[]) { return 0; } 数据类型 函数名(形式参数) { 代码块 return 返回值; } 数据类型:和返回值所对应,返回值是什么类型,数据类型就写什么类型,当数据类型为void类型的时候,函数没有返回值,如果不写,则默认为int类型 函数名:最好见名知意,并且和标识符的命名规则保持一致 形式参数:将实际参数的值拷贝一份过来放到形式参数里,并且形式参数在进入函数时声明定义初始化,函数结束时释放,相当于局部变量;数据类型不能省略。 返回值:和数据类型保持一致,可以写常量,也可以写变量。
函数的声明和定义
函数的定义要放在主函数上面;如果放在主函数下面需要先将函数声明; void func(void); int main() { return 0; } void func(void) { }
函数的使用
函数名(实际参数);
#include <stdio.h>
int func(char a, int n)
{
//char a = 'Q';
int i = 0;
for(i = 0; i < n; i++)
printf("%c", a);
putchar('\n');
return n;
}
int main(int argc, char *argv[])
{
char a = 'Q';
int temp = func(a, 7);
printf("temp:%d\n", temp);
//putchar('\n');
//putchar(a);
//putchar('\n');
return 0;
}
函数的形式参数
1、值传递
传递普通变量时,只会把变量的值拷贝一份给形式参数,但变量本身没有传递给形参
2、地址传递
将变量的地址传递给形式参数,可以通过直接操作内存地址的方式来操作原本变量
3、全局变量
全局变量寿命周期长,可以用于函数中
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a/b;
}
void swap(int *a, int *b)
{
int c = 0;
c = *a;
*a = *b;
*b = c;
}
int c = 10;
int d = 20;
void swap2(int a, int b)
{
int temp = 0;
temp = a;
a = b;
b = temp;
}
int main(int argc, char *argv[])
{
int a = 10;
int b = 20;
printf("before-------\n");
printf("a:%d b:%d\n", a, b);
swap2(c, d);
printf("after--------\n");
printf("c:%d d:%d\n", c, d);
return 0;
}
函数的形式参数不仅能传普通的变量,还可以传地址进去
例: 如果想在形式参数中设置地址传递: void func(int *a, int *b); 或者数组名也可以作为地址传递进去,作为地址传递 int *p[3]; 如果要传递&p[0]为参数则函数应该以int **的类型来接受 void func1(int **a, int *b);
函数的返回值能不能写地址?(即函数的数据类型能不能作为指针变量类型)?
指针函数
基本形式: int * 函数名(形式参数) { 代码块; return 地址; } 本质是一个函数,返回值为地址(指针) return返回的地址 1、全局变量 2、static修饰的局部变量 3、字符串常量的首地址 4、形式参数传递进来的地址 核心思想:不允许返回一个没有被申请使用权的地址,即不能去非法访问内存 非法访问内存:使用了未被申请的内存空间,如数组的越界访问 内存泄露:申请了内存空间后,没有进行释放 内存溢出:内存空间满了,申请空间时申请失败
函数指针
本质是一个指针,用于保持函数的入口,可以通过指针来调用函数 基本形式: 函数的数据类型 *函数指针名(形式参数) int (*p)(int a, int b) = add; p(形式参数1,形式参数2) == add(形式参数1,形式参数2 形式参数的个数和类型必须和函数保持一致
回调函数
将一个函数的函数名作为参数传入另一个函数中供其使用
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int mul(int a,int b)
{
return a*b;
}
int div(int a,int b)
{
return a/b;
}
int jjcc(int a, int b, int (*p)(int c,int d))
{
return p(a,b);
}
int main(int argc, char *argv[])
{
//int (*p)(int a,int b) = add;
//printf("%d\n", p(10,20));
printf("%d\n",jjcc(20,10,sub));
return 0;
}