ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

C语言重难点-关于数组、指针、递归、结构体

2021-08-25 12:31:21  阅读:160  来源: 互联网

标签:10 arr 递归 int C语言 重难点 地址 数组 指针


https://www.zhihu.com/people/liu-nian-19-58

写在前面:持续更新,这篇文章针对C语言里面的几大重难点分享自己的笔记,希望对你有所帮助。

如果觉得对你有帮助,可以点点赞,加个收藏。

有什么疑问可以在评论区留言一起讨论。

别下次一定了,笔芯~~~

一、数组

1. 定义

数组是一组相同类型元素的集合,它在内存中是连续存放的。创建方式为:
type_t arr_name [const_n] ,如:

int arr[5]
char arr[3]
double arr[10]

2.初始化:

  • 不完全初始化:int arr[5]={1,2,3} 剩下的元素默认为0;
  • 未指定数组长度:int arr[]={1,2,3,4}
  • 字符串形式初始化:char arr[]='abcd'

补充:sizeof和strlen
sizeof:“sizeof()”运算符求的是字符数组的长度,而不是字符串长度。只跟你给该字符串数组定义了多大空间有关,而跟字符串是否结束无关.如果遇到字符串,编译时会自动在末尾增加一个 null 字符,即char arr1[]='abc\0'。
strlen:用来计算以’\0’结尾的字符串长度的函数。它并不是计算内存大小,仅计算字符串从开端到’\0’结尾字符的个数(不包含’\0’)。
char arr1[]='abc';
char arr2[]={'a','b','c'}
sizeof(arr1)=4
sizeof(arr2)=3
strlen(arr1)=3
strlen(arr2)=随机数
其中,arr1[]是字符串,arr2[]是字符数组
总结:以字符串形式出现的,编译器都会为该字符串自动添加一个0作为结束符,如在代码中写 "abc",那么编译器帮你存储的是"abc/0",char arr[]="abc"实际上存储的是 char arr[]={'a','b','c',''\0}

3.二维数组

3.1 创建方式

数据类型 数组名称[行][列],如:int arr[3][2]代表三行两列的数组

3.2 初始化

  • 不完全初始化:int arr[3][2]={1,2,3} 剩下的元素默认为0;
    1 2
    3 0
    0 0
  • 指定行列:int arr[3][4]=={{1,2,3},{4,5}}
    1 2 3 0
    4 5 0 0
    0 0 0 0

3.3 使用

访问元素:
for(int i=0;i<3;i++){
for(int j=0;j<4;j++){
print("%d",arr[i][j]);
}
print("\n")
}
数组作为函数参数:
void bubble_sort(int arr[],int sz){
{
...
}
int main(){
int arr[]={1,2,3,4,5};
bubble_sort(arr,sz);//我们对arr进行传参,实际上传递过去的是数组的首元素的地址即&arr[0];
int sz=sizeof(arr)/sizeof(arr[0]);
...
return 0;
}

补充1:关于sizeof(arr)/sizeof(arr[0])
sizeof(arr)计算的是数组arr所占的总字节数,即空间大小;
sizeof(arr[0])是单个元素的大小;
sizeof(arr)/sizeof(arr[0])就是数组的长度;
如:int arr[]={1,2,3,4,5}
数组长度:sizeof(arr)/sizeof(arr[0])
其中,整数 int占4个字节,总字节数/4就是数组长度;
char arr[]={'a','b','c'}
数组长度:sizeof(arr)/sizeof(arr[0])
其字母占1个字节,故可简写成:sizeof(arr)。

补充2:&数组名、&数组名【】
&数组名:取出的是整个数组的地址(打印出首元素地址作为整个地址地代表)
&数组名[0]:取出的是数组的首元素地址
数组名:取出的是数组的首元素地址
注意
①绝大多数时候,&arr[0]和arr都是首元素地址,但是也有例外:
sizeof(arr)/sizeof(arr[0]);arr表示整个数组,sizeof(arr)表示整个数组的大小。
②int arr[10]={0}

二、指针

1.1 定义

指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中的另一个地方的值,地址指向变量单元,存放地址的变量就是指针变量,换句话说,指针就是一个变量,里面存放着地址,指针就是地址。
如:
int a=10
int *p=&a//p是一个指针变量
prunt(*p)// *是解引用,取指针p指向的地址里的内容,*p=10
指针的大小在32位平台是四个字节,在64位平台是八个字节。

1.2 指针和指针类型

(1)指针类型的意义

①指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。

  • int*p: *p能够访问4个字节
  • char *p:*p能够访问1个字节
  • double* p:*p能够访问8个字节
  • ②指针类型决定了指针走一步走多远(指针的步长)
  • int*p: p+1-->往后4字节
  • char *p:p+1-->往后1字节
  • double* p:p+1-->往后8字节

(2)野指针

指针执行的位置是不可知的
导致野指针的原因:

  • 未初始化,局部变量不初始化,默认是随机值
  • 指针越界访问
  • 指针指向的空间释放

怎么避免野指针:

  • 指针初始化
  • 小心指针越界
  • 指针指向空间释放的话,使之置为NULL
  • 指针使用之前检查有效性

补充
① i++与++i
区别一:i++是右值,++i是左值,左值是可以放到赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量,而右值i++不可以。比如说:
int i=0;
++i=1;//正确
i++=1;//错误
左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址,左值允许,右值不允许。如
&(++i)//正确
& (i++) //错误
为什么++i允许,而i++不允许呢?

C/C++语言中可以放在赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量。左值表示存储在计算机内存的对象,而不是常量或计算的结果。或者说左值是代表一个内存地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作;这也就是为什么左值可以被赋值的原因了。相对应的还有右值:当一个符号或者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值。简单来说就是, 左值相当于地址值,右值相当于数据值.
区别二:i++是先运算后自加;++i是先自加后运算。比如说:
i=3
n=i++,此时,n=3,i=4(先赋值运算,后加1)
n=++i,此时,n=4,i=4(先加1,后赋值运算)

②指针+-整数
float arr[5];
float *vp;//定义一个指针变量
for (vp=&arr[0]; vp<arr[5]; ){
. *vp++ = 0;
}
指针vp指向数组arr的首元素地址,vp++=0先赋值给vp为0,在vp+1指向第二个元素,第二个元素=0;直至第五个元素也为0.

③指针-指针(地址-地址)
必须是同类型指针
int arr[5]={1,2,3,4,5}
&arr[5]-&arr[0]=5//结果是两指针中间的元素个数

④指针比较大小
法一:
for(vp = &arr[5];vp>&arr[0]; ){
*--vp = 0;
}
法二:
for(vp = &arr[5-1];vp>=&arr[0];vp-- ){
*vp = 0;
}


但是更推荐第一种方法,标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较即法一,不允许与指向第一个元素之前的那个内存位置的指针进行比较。

1.4 二级指针

1.4.1 定义

int a=10;
int * p1 = &a;//一级指针,int*分开,int表示p1指向的对象类型是int整形,*表示p1是指针
int* * p2=&p1//二级指针,int*表示p2指向的对象类型是int*指针即p1,右边的*表示p2是一个指针;

1.4.2 用法

解引用:
*p1=**p2=a=10
*p2=p1

1.4.3 指针与数组

#####(1)指针数组
指针数组就是存放指针的数组。
int a = 10;
int b = 20;
int c = 30;
int* pa=&a;
int* pb=&b;
int* pc=&c;
为了方便,我们可以将pa,pb,pc指针存放在一个数组中。
int* arr[3]={&a,&b,&c}
int* arr[3]={pa,pb,pc}
遍历访问元素:
int i=0;
for(i=0;i<3,i++){
. *(arr[i])
}

(2)数组指针

存放数组的指针。

三、指针进阶

1.1 字符指针

1.1.1 定义

法一:
char ch = 'abc;
char* pc = &ch;
法二:
char* p = "abc"//把常量字符串“abc”的首元素a的地址放进了p中,而不是内容abc
这个严格来说应该这么写:const char* p="abc",理由后面介绍。
补充:字符数组和字符指针
(1)字符数组:
char arr1[4]="abcd"
char arr2[4]="abcd"
定义的是一个字符数组,所以就相当于定义了一些空间来存放"abcd",而又因为字符数组就是把字符一个一个地存放的,所以编译器把这个语句解析为 char arr[5] = {'a','b','c','d','\0'}; 回顾之前到讲到的,sizeof(arr[5])=5; 扩展一下,如果char arr[] = "abcd"是在函数内部写的话,那么这里 的"abcd/0"因为不是常量,所以应该被放在栈上
另外,arr1!=arr2,因为arr1,arr2分别定义了各自的空间来存储内容,这里恰巧两个的字符数组的内容一样而已。故,两者不一样。
(2)字符指针:
char* p1="abcd"
char* p2="abcd"
定义的是一个普通指针,并没有定义空间来存放"abcd",所以编译器得帮我们找地方来放"abcd",显然,把这里的"abcd"当成常量并把它放到程序 的常量区是编译器最合适的选择。拓展一下,
字符指针指向的字符串保存在内存的静态存储区中。
因为是常量字符串,如下操作:
char* p1="abcd"
p1=“h”
错误,常量字符串不可修改。
另外,p1==p2,因为p1,p2都是常量,内容都是“abcd”,都指向同一个内存空间。
此处,为避免错误,还是写成const char p="abcd"为好。
总结一下就是
首先在内存的中位置不同,字符数组保存的字符串存放在内存的栈中,而字符指针指向的字符串保存在内存的静态存储区中。
其次字符数组保存的字符串属于字符串变量,可以被修改,而字符指针指向的字符串是属于字符串常量,不能被修改。

----------------------------------------8.22更新---------------------------------------------

1.2 指针数组

1.2.1定义

指针数组是一个数组,用来存放指针。
int* p[10]={0}//存放整形指针的数组-指针数组
char* p[10]={0}//存放字符指针的数组-指针数组

1.2.2 使用

指针数组访问每个元素:

1.3 数组指针

数组指针是一个指针,(*p),用来指向数组的指针。
int* p=NULL //p是整形指针-指向整型的指针-存放整形的地址
char* pc=NULL //pc是字符指针-指向字符的指针-存放字符的指针
int (*p )[10]=&arr //数组指针-指向数组的指针-存放数组的地址
关于数组的地址,前面有讲过,即&arr。
书写方法:
char* arr[5];
char* (*p)[5]=&arr;
int arr2[10]={0];
int (*p2)[10]=&arr2;

补充:关于*星号
①在定义变量时,代表着该变量是一个指针
int a=10;
int* p=&a;
②在取值操作时,叫解引用,即得到指针指向的地址的内容
*p=a=10

遍历方法:
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int* p=arr;
int i=0;
for(i=0;i<10;i++){
. printf("%d", *(p+i))
. printf("%d", *(arr+i))//用指针的方法打印
. printf("%d", arr[i])//普通的数组打印方式
. printf("%d",p[i])
四种打方式结果一样
}
关于二维数组
二维数组的数组名是首元素的地址,这里的首元素不是第一行第一列的元素,而是第一行所有的元素。(这里把二维数组理解成特殊的一维数组)
遍历二维数组的元素:
法一:
参数是数组的形式


法二(用数组指针):
参数是指针的形式


难点:解释下为什么是*(*(p+i)+j):


补充:
*(*(p+i)+j)的等效写法:
①((p+i)[j])//备注:*(p+i)其实就是一个数组首元素的地址,用指针名[下标]找到数组某个元素。*(p+i)=p[i]
②*(p[i]+j)
③p[i][j]

1.4关于以上几种类型的总结

①int arr[5]//arr是一个5个元素的整型数组
②int* parr1[10]//parr1是一个数组,数组有10个元素,每个元素的类型是int*, parr1是指针数组
③int(* parr2)[10]//parr2是一个指针,该指针指向了一个数组,该数组有10个元素,每个元素的类型是int, parr2是数组指针
④int (* parr3[10])[5])//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素的类型是int。

-----------------------------------------8.23更新-------------------------------------------

1.5 数组参数

1.5.1 一维数组

(1)数组在传参的时候可以将参数写成数组,也可以写成指针。如
void test(int arr[])
{ }
void test(int arr[10])
{ }
void test(int *arr);
{ }
int main(){
int arr[10]={0};
test(arr);
}
这三种传参都是正确的。
(2)指针数组在传参的时候可以将参数写成数组,也可以写成指针。如
void test(int* arr[])//数组类型是int*
{ }
void test(int* arr[10])
{ }
void test(int** arr);
{ }
int main(){
int* arr[10]={0};
test(arr);
return 0;
}
这三种传参也是正确的。

1.5.2 二维数组

(1)数组名写法
void test (int arr[3][5]) // 写成int arr[][5],不可以写成int arr[3][],行可以省略,列不可以省略
{ }
int main(){
int arr[3][5]={0};
test(arr);
return 0;
}
(2)指针写法
void test (int* arr)//写法错误,整形指针只存放整形,不能存放数组,而arr是二维数组的首元素地址,也就是第一行数组的地址
{ }
void test (int** arr)//写法错误,二级指针是用来存放一级指针的地址,而arr是一个数组的地址
{ }
void test (int* arr[5])//写法错误,arr是一个数组,每个元素类型是int*
{ }
void test (int(* arr)[5])//写法正确,arr是一个指针,指向第一行数组的五个元素,类型是int
{ }

int main(){
int arr[3][5]={0};
test(arr);
return 0;
}

------------------------------------- 8.24更新----------------------------------------------

1.6 指针传参

1.6.1 一级指针传参

void test1(int* p)//传过来的是地址(整形指针),所以这里要用一个指针来接收
{}
int main()
{
int a=10;
'test1(&a);//传过去的是地址
int* p=&a;
test1(p);//传过去的是a的地址,将a的地址存在指针变量p里面
}

1.6.2 二级指针传参

void test1(int** ptr)//传过来的是一级指针的地址,所以这里要用一个二级指针来接收
{}
int main()
{
int a=10;
int* p=&a;
int** pp=&p//pp是二级指针;
test1(pp);//传过去的是一级指针p的地址,将p的地址存在二级指针变量pp里面
test1(&p);
int* arr[10];//定义一个指针数组,里面存放着一级指针
test1(arr)//arr是数组首元素地址,也就是一级指针的地址
}
故,当函数的参数为二级指针得时候,参数可以是:

  • 一级指针变量的地址
  • 二级指针变量本身
  • 存放一级指针的指针数组的数组名

1.7 函数指针

数组指针-指向数组的指针-存放数组的地址- int (* p)[10]
函数指针-指向函数的指针-存放函数的地址- int (* p)(intx, inty)
补充
①&函数名和函数名都是函数的地址

---------------------------------8.25更新-----------------------------------------------

②( *( void ( * )( ) ) 0 )( )
把0强制类型转换成:void()()函数指针类型,0就是一个函数的地址。(*(...)0)()调用0地址处的该函数。


void (*signal(int , void(*)(int) ) )( int )
signal是一个函数声明,signal的函数有2个参数,一个是int,一个是void(*)(int)函数指针,该函数指针指向的函数的参数类型是int,返回类型是void。
signal返回类型也是一个函数指针,该函数指针指向的函数的参数类型是int,返回类型是void。


该代码可以简化成:
typedef void(*pfun_t)(int);
pfun_t signal(int,pfun_t);

1.8 函数指针数组

指针数组-int* arr[5]
函数指针-int(* p)(int int)=ADD//函数指针的返回类型是int
函数指针数组-存放多个函数的地址即函数指针的地址-int (* parr[4])(int ,int)={ADD,SUB,MUL,DIV} : parr是一个数组,有四个元素,每个元素的类型是函数指针。
使用方法:


计算器案列:

标签:10,arr,递归,int,C语言,重难点,地址,数组,指针
来源: https://blog.csdn.net/qq_52717033/article/details/119908623

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有