补充1:优先级,解答问题区分*p++,*p++=0,*++p=0
<1>.首先要理解前后置++的区别,前置++,先计算后使用;后置++,先使用后计算。
可用示例理解:
<2>.其次要理解操作符的优先级
优先级从高到低:++(后置++)>*(解引用)>=(赋值操作符)
<3>.从<1><2>来解读:
*p++:++优先级高,先发挥作用,作用于p,故p先使用后++。故可拆分为这样的两个过程:1.*p;2.p++
*p++=0:可通俗理解为这样的三个过程:第一:*p,第二:(*p)=0,第三:p++(但实际上是++先发挥作用)
*++p=0:可通俗理解为这样的三个过程:第一:++p,第二:*(++p)第三:*(++p)=0.
补充2:地址和字节,一个单元就是一个字节,每个字节都是有他的门牌号儿的,就是它的地址。
int a=10;
scanf("%d\n",&a);//&a取的是一个地址,这个地址就是首字节(一共4个)的地址
1.自定义函数(有函数名,返回值类型(返回值可有可无),函数参数)
补1:函数的嵌套调用和链式访问(把一个函数的返回值作为另外一个函数的参数)
补2:函数声明:<1>函数后定义则要先声明
例如:
int Add(int x,int y);//结尾加分号
int main()
{
int a=10;
int b=20;
printf("%d\n",Add(a,b));
return 0;
}
int Add(int x,int y)
{
int z=x+y;
return z;
}
<2>告诉编译器一个函数叫什么,参数是什么,返回类型是什么,但是具体是不是存在,无关紧要
<3>函数的声明一般放在头文件中,引自己写的头文件用双引号,引库里头的头文件用尖括号 (一般在.h头文件中声明函数,函数的定义实现放在.c文件里,如果要用,引一下头文件)
补3:函数递归(recursion):一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法(一个函数自己调用自己),递归策略只需要少量程序就可描述出解题过程所需要的多次重复计算(化大为小)
<1>顺序问题:(顺序结构)要把函数名所代表的整个函数放到这里,按顺序结构执行代码.
援引苏铭宇“递归就是从外往里进行,从里往外输出”。
例子:(输入1234隔开打印1 2 3 4)
void print(int n)
{
if (n > 9)//n至少两位
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;//无符号整形unsigned int,有符号整形(有正有负)用int
scanf("%d",&num);
print(num);
return 0;
}
<2>局部变量和全局变量的作用域(同一字母代表不同的值)
<3>递归的两个必要条件:1.存在限制条件,当满足这个条件时,递归不再继续。2.每次递归之后越来越接近于这个限制条件。
<4>递归与迭代(补充递归和循环的区别,援引万山哥“循环就是简单的重复一件事儿,迭代是以上一次的结果不断递进。”)
例子:累乘代码:
int mul(int n)//循环写法
{
int i = 1;
int ret = 1;
for (i = 1; i <= n; i++)
ret *=i;
return ret;
}
int mul2(int n)//递归写法
{
if (n > 1)
return n*mul2(n-1);
else
return 1;
}
int main()
{
int n;
scanf("%d", &n);
printf("ret=%d\n", mul2(n));
return 0;
}
如果用递归求斐波那契数列的通项:
//定义在{ }(代码块)之外的变量称为全局变量
//定义在{ }(代码块)之内的变量称为局部变量
//当局部变量与全局变量的名字相同的时候,局部变量优先
//限定一段代码中所用到的名字的可用性的代码范围就是这个名字的作用域
//局部变量的作用域是变量所在的局部范围
//全局变量的作用域是整个工程
//生命周期:变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
//局部变量的生命周期是进入作用域生命周期开始,出它的作用域生命周期结束
//全局变量的生命周期是整个程序的生命周期
int count = 0;//全局变量
int Fib(int n)
{
if (n == 3)
count++;//创建循环配合全局变量说明递归造成大量重复计算
if (n == 1 || n == 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
//int count = 0;
int n = 0;
scanf("%d", &n);
printf("%d\n", Fib(n));
printf("count=%d\n", count);
return 0;
}
这个代码的问题是:大量重复计算,如果要算第30个斐波那契数,仅第三个斐波那契数就被计算了317811次,效率极低,计算速度慢
改善后代码:
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 0;
if (n == 1 || n == 2)
return 1;
else
{
while (n>2)//减少运算次数
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", Fib(n));
return 0;
}
补4:数组:数组是一组相同类型元素的集合
^1.数组的创建方式
type_t arr_name [const_n]
//type_t是指数组元素类型
//const_n 是一个常量表达式,用来指定数组的大小
^2.数组的初始化
数组的初始化指,在创建数组的同时给数组的内容一些合理的初始值(初始化)
int char[10]={1,2,3};
char arr2[5]={'a','b'};
char arr2[5]="ab";//实际放进数组里面的元素有三个,a,b,\0(取值为0),剩余两个元素默认为0
//不完全初始化,剩下的元素默认初始化为0
char arr3[5]={'a',98};//b的ascii码值是98
char arr4[]="abcdef";//没有指定数组大小,则根据初始化的内容自动确定大小
printf("%d\n",sizeof(arr4));//sizeof是计算arr4(7个元素)所占空间的大小
printf("%d\n",strlen(arr4));//strlen是计算arr4中字符串长度*(字符个数),遇'\0'停止且不算'\0'
//所以结果为7,6。此处对比sizeof和strlen
//1.sizeof和strlen没有什么关联
//2.strlen是求字符串长度的,只能针对字符串求长度,库函数,使用时引头文件
//3.sizeof计算变量,数组,类型的大小(单位是字节),操作符,不需要引头文件
^3.一维数组的使用
下标引用操作符[ ],即数组访问的操作符。数组是使用下标来访问的,下标从0开始。数组的大小(sizeof)可以通过计算得到。
^4.一维数组的内存存储
main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
printf("&arr[%d]=%p\n", i, &arr[i]);
}
总结:随数组下标增长,元素的地址也有规律性的递增,由此可见,数组在内存中连续存放。
^5.二维数组的创建和初始化
//数组创建
int arr[3][4];:3行4列
char arr[3][5];
double arr[2][4];
//二维数组的初始化
int arr[3][4]={1,2,3,4};
int arr[3][4]={{1,2},{3,4}};
int arr[ ][4]={{1,2},{3,4}};
^6.二维数组的使用
也是通过下标(注意下标从0开始,而计数从1开始)
int a;
int i;
main()
{
int arr[3][4] = { { 1, 2, 3 }, { 4, 5 } };
for (a = 0; a < 3; a++)
{
for (i = 0; i < 4; i++)
{
printf("%d ", arr[a][i]);//打印一个二维数组
}
printf("\n");
}
}
^7.二维数组在内存中的存储
二维数组内存也是连续的,可用代码验证
int a;
int i;
main()
{
int arr[3][4] = { { 1, 2, 3 }, { 4, 5 } };
for (a = 0; a < 3; a++)
{
for (i = 0; i < 4; i++)
{
printf("&arr[%d][%d]=%p\n",a,i, &arr[a][i]);//打印一个二维数组各元素地址
}
}
}
由此可得:
^8.数组作为函数参数
将数组作为参数传给某个函数,例如:冒泡排序算法(函数将一个整型数组排序)
int c;
int i;
int tmp;
sort1(int arr[], int sz)
{
for (i = 0; i < sz - 1; i++)//共(sz-1)次
{
for (c = 0; c < sz-1-i;c++)//第一次要比(sz-1)回,而第(sz-1)次只需要比较一回(比较当前数列中前两个数
{
if (arr[c] > arr[c + 1])
{
tmp = arr[c];
arr[c] = arr[c + 1];
arr[c + 1] = tmp;
}
}
}
}
sort2(int* p, int sz)
{
for (i = 0; i < sz - 1; i++)//共(sz-1)次
{
int flag = 1;//假设这一次要排序的数据已经有序
for (c = 0; c < sz - 1 - i; c++)//第一次要比(sz-1)回,而第(sz-1)次只需要比较一回(比较当前数列中前两个数
{
if (p[c] > p[c + 1])
{
tmp = p[c];
p[c] = p[c + 1];
p[c + 1] = tmp;
flag = 0;//此次排序的数据不完全有序
}
}
if (flag == 1)//用flag提高了效率
{
break;
}
}
}
main()
{
int arr[]= {100,98,82,71,969,25,14,23,42,61};
int sz = sizeof(arr) / sizeof(arr[0]);
sort2(arr,sz);//此处写sort1或sort2皆可
for (i = 0; i < sz ;i++)
printf("%d ", arr[i]);
}
提出问题:arr数组并没有传过去,但在自定义函数中为什么能用arr[下标]表示元素呢?
援引苏铭宇1.“c语言为了方便阅读,允许形参指针形式写成数组形式,这只是为了更加方便理解,就是你传了个数组首元素,你可以用*p或者arr[]来接收,在用的时候你也可以p[]或者p+i或者arr[]或者arr+i”。
2.形参的数组名代表数组首元素的地址啊,那我通过这个地址就可以去解锁到这里面儿每一个下标的元素。
^9.数组的应用事例(<1>三子棋<2>扫雷)
<1>三子棋(单独发表博客)
<2>扫雷(单独发表博客)
补5:指针:由于通过地址能找到所需的变量单元,可以说地址指向该变量单元。因此地址形象化+地称为指针。 存放在指针中的值都被当成地址处理。
int main()
{
int a=10;//在内存中开辟一块空间
int* p=&a;//这里我们对变量a,取出它的地址,可以用&操作符
//p是指针变量(p是一个指针),用来存放地址
//将a的地址存放在p变量中
return 0;
}
<1>指针类型的第一个意义:指针类型决定了指针进行解引用操作的时候能够访问空间的大小。
int*p:*p能访问4个字节
char*p:*p能访问1个字节
double*p:*p能访问8个字节
int main()
{
int a = 0x11223344;
int* pa = &a;//可换成char* pa=&a,通过调试观察内存的变化
*pa = 0;
return 0;
}
<2>指针类型的第二个意义:指针加减整数,指针类型决定了指针走一步走多远(即指针的步长),单位是字节。
int*p:p+1向<>后偏移4个字节
char*p:p+1向后偏移1个字节
double*p:p+1向后偏移8个字节
<3>野指针:野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
哪些情况可能导致野指针呢?
^1.指针未初始化(如果不知道指针指向谁,用NULL空指针帮助初始化)
int main()
{
int a;//局部变量不初始化,默认是随机值。
int* p;//p是局部的指针变量,就被初始化随机值
*p = 20;
return 0;
}
^2.指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 12; i++)
*(p++)=i;//当指针指向范围超出数组arr的范围时,p就是野指针
return 0;
}
^3.指针指向内存空间释放
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p=test();
*p = 20;//访问的空间已经还给系统了
return 0;
}
如何规避野指针呢?
*1.指针初始化。
*2.小心指针越界
*3.指针指向空间释放就把指针置为NULL
*4.指针使用前检查有效性(指针是否为空指针)
int main()
{
int* p =NULL;
int a = 10;
p =&a ;
if (p!= NULL)//检查指针有效性
{
*p=20;
}
return 0;
}
<4>.指针运算
^1.指针加减(+-)整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = &arr[9];
int i;
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p--;
}
return 0;
}
//该代码基本结构为:
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
^2.指针减(-)指针,即是地址减地址,可依此计算数组元素个数(详见下面的计算字符串长度)
^3.指针的关系运算(即比较大小)
<5>指针和数组
^1.数组名在大多数情况下是首元素地址,可用代码佐证
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}//打印出相同的结果
但有两个例外,第一:在&arr(&数组名)中数组名不是首元素地址,数组名表示整个数组。&arr是整个数组的地址 第二:sizeof(arr)【即sizeof(数组名)】,数组名表示整个数组。 sizeof(数组名)计算的是整个数组的大小
2.函数的组成
ret_type fun_name(paral,*)
(
statement;
)
ret_type返回类型
fun_name函数名
paral 函数参数
int Add(int x,int y)//第一个int是返回类型
{ //Add是函数名
int z=0; //int x,int y是函数参数
z=x+y; //{ }里面是函数体,交代函数如何实现
return 0;
}
如果自定义的函数没有返回类型,那么编译器默认返回一个int。
如果返回类型写void,那么这个函数不需要返回值。
3.例子:例一(交换参数的值)
Swap(int x,int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 20;
printf("a=%d b=%d\n", a,b);
Swap(a,b);
printf("a=%d b=%d\n", a, b);
return 0;
}
结果:
不能交换原因:传参即是传参数的数值,但是x,y和a,b的地址不同
解决方法:由传参的本质在于传值,故可以地址作为桥梁,然后进行解引用操作来改变a,b的值。
可借助表格来理解
\ | a | b |
数值 | 10(*b)(*&a) | &a |
地址 | &a | &b |
void Swap(int* x,int* y)//x和y是形式参数(形参)
{
int tmp = 0;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 0;
int b = 20;
printf("a=%d b=%d\n", a,b);
Swap(&a,&b); //&a和&b是实际参数(实参)
printf("a=%d b=%d\n", a,b);
return 0;
}
结果:
补充:^1.(关于形参和实参)
实参可以是:常量,变量,表达式(例如:300+1),函数(整体思想,例如:get_max(1,2)作为整体)等,在进行函数调用时,实参必须有确定的值,以便把这些值传给形参。
形参是函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数,形式参数当函数调用完之后就自动销毁,因此形式参数只在函数中有效。
实参与形参的关系:实参实例化之后其实相当于实参的一份临时拷贝。
^ 2.(关于函数的调用:传值调用和传址调用)
传值调用:对形参的修改不会影响实参
传址调用:把函数外部创建变量的内存地址传递给函数参数的一种调用方式。
函数内部可以直接操作函数外部的变量。
例二:编函数找出100-200内的素数
return 0;一执行自定义函数结束。
int is_prime(int n)
{
int x = 2;
for (x = 2; x <= n / 2; x++)
{
if (n%x == 0)
return 0;
}
return 1;
}
int main()
{
int i = 0;
for (i = 100; i < 200; i++)
{
if (is_prime(i) == 1)
printf("%d ", i);
}
return 0;
}
例三:二分查找,并计算调用函数的次数
int binary_search(int arr[], int k,int sz,int* pcount)//arr[ ]可以不指定大小,因为本质上不是一个数组,是数组arr首元素的地址(即指针)
{
int left = 0;//
//int sz = sizeof(arr) / sizeof(arr[0]) - 1;//数组在传参的时候,在函数内部不能用这样的方式求元素个数,因为数组在传参的时候,并不是传整个数组,仅传递数组第一个元素的地址
//sizeof去计算指针大小的时候就是4或8,因为是Win32所以是4个字节,arr[0]是第一个元素,第一个元素是整形,所以它也是4个字节。故sz=1
int right = sz-1;//计算机除法运算法则:6/4得到的是整数1,而6.0/4得到的是小数1.5
while (left <= right)
{
int mid = (right + left) / 2;
if (arr[mid] > k)
right = mid - 1;
else if (arr[mid] < k)
left = mid + 1;
else
{
*pcount++;
return mid;
}
}
*pcount++;
return 0;
}
int main()
{
int k = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
scanf("%d", &k);
int sz = sizeof(arr) / sizeof(arr[0]);
int count = 0;
int ret = binary_search(arr,k,sz,&count);
if (ret==0)
printf("找不到,调用函数次数为:%d\n",count);
else
printf("找到了,所找数的下标是:%d\n调用函数次数为:%d", ret,count);
return 0;
}
//在函数内部算元素个数不靠谱,所以在函数外部算个数
问题:不能计算出函数调用次数,例如:若输入5则结果为
原因:优先级的问题,*p++,++的优先级高,作用于p,
解决方案:加括号,*p++改为(*p)++。
正确代码:
int binary_search(int arr[], int k,int sz,int* pcount)
{
int left = 0;
int right = sz-1;
while (left <= right)
{
int mid = (right + left) / 2;
if (arr[mid] > k)
right = mid - 1;
else if (arr[mid] < k)
left = mid + 1;
else
{
(*pcount)++;
return mid;
}
}
(*pcount)++;
return 0;
}
int main()
{
int k = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
scanf("%d", &k);
int sz = sizeof(arr) / sizeof(arr[0]);
int count = 0;
int ret = binary_search(arr,k,sz,&count);
if (ret==0)
printf("找不到,调用函数次数为:%d\n",count);
else
printf("找到了,所找数的下标是:%d\n调用函数次数为:%d", ret,count);
return 0;
}
补充说明优先级问题:(暂时有疑问:无法区分*p++,*p++=0,*++p=0) (解答见文首)
int main()
{
int a[10] = { 0 };
int* i = 0;
int* p = a;
for (i = 0; i <= 12; i++)
{
*p++ = i;//等同于*p=i; p++;
} //p先++,*作用于p,为*p=i;
return 0;
}
例四:求字符串的长度‘(\0’是字符串的结束标志,但是不属于字符串的内容,strlen函数只算字符串'\0'之前的长度)
^1.计数器方法(编写代码创建临时变量,求字符串的长度)
int mystrlen(char* str)
{
int count = 0;
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "bit";
printf("%d\n",mystrlen(arr));//数组传参传过去的是第一个元素的地址
return 0;
}
^2.递归方法(编写代码不能创建临时变量,求字符串的长度)
我自己的思考:
my_strlen(char* str,int* pcount)
{
if (*str != '\0')
{
my_strlen(str+1,pcount);
(*pcount)++;
}
}
int main()
{
char arr[] = "bit";
int count =0;
my_strlen(arr, &count);
printf("%d\0", count);
return 0;
}
如果运用有返回值的递归:
int my_strlen(char* str)
{
if (*str != '\0')
return (1 + my_strlen(str + 1));
else
return 0;
}
int main()
{
char arr[] = "bit";
printf("%d\n", my_strlen(arr));
return 0;
}
^3.指针运算(指针减指针)
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while (*end != '\0')
end++;
return end - start;//不算'\0'
}
int main()
{
char arr[] = "bit";
printf("%d\n", my_strlen(arr));
return 0;
}
我自己简化了:
int my_strlen(char* arr,char* p)
{
return p-arr;
}
int main()
{
char arr[] = "bit";
int sz = sizeof(arr) / sizeof(arr[1]);
printf("%d\n", my_strlen(arr,&arr[sz-1]));//不算'\0',如果去掉-1就算
return 0;
}