一、指针部分
1.指针和二维数组
二维数组的存储方式:
在内存中,二维数组是按行优先的顺序连续存储的。例如,对于一个m*n的二维数组arr[m][n]
,数组元素会先存储第一行的所有元素,接着存储第二行的元素,依此类推。
1.1指针访问二维数组元素
使用指向数组元素的指针:
可以定义一个指向二维数组元素类型的指针,然后通过指针的偏移来访问二维数组的元素。
语法格式:
数据类型 (*变量名)[数组中元素的个数];
如果让数组指针指向二维数组,那么[]中的数据和二维数组的列数保持一致
因为数组指针和二维数组的偏移量都是一整行元素,所以数组指针可以直接指向二维数组。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int arr[2][3]={23,13,8,34,9,2};
int arr1[4]={1,9,4,2};
printf("arr=%p\tarr+1=%p\n",arr,arr+1); //偏移量是12
printf("arr1=%p\tarr1+1=%p\n",arr1,arr1+1); //偏移量是4
int *p=arr; //让int*类型的指针指向二维数组,因为偏移量不一致会报警告
printf("p=%p\tp+1=%p\n",p,p+1);
return 0;
}
数组指针指向二维数组
#include <stdio.h>
int main(int argc, const char *argv[])
{
int arr[2][3]={12,90,34,
2,81,52};
//定义一个数组指针指向二维数组
int (*p)[3]=arr;
//[i]<==>*(+i)
printf("arr[1][2]=%d\n",arr[1][2]);
//arr[1][2]<==>*(*(arr+1)+2)
//arr[0][0]<==>**arr
printf("*(arr+1)=%p\tarr[1]=%p\t&arr[1][0]=%p\n",*(arr+1),arr[1],&arr[1][0]);
//p+1表示指向第二行一整行元素,*(p+1)表示指向第二行第一个元素
printf("*p=%p\t*(p+1)=%p\n",*p,*(p+1));
printf("%p\n",*p); //第一行第一列元素的首地址
printf("%p\n",*p+2); //*p先结合,*p表示第一行第一列元素的首地址,
//*p+2表示对第一行第一列的列地址向后偏移两个数据单位
//*p+2表示第一行第三个元素的首地址
printf("%p\n",*(arr+1)+2); //arr+1行地址偏移一行元素,偏移到第二行
//*(arr+1) --->取*降级成列地址,表示第二行第一个元素的地址
//*(arr+1)+2 列地址向后偏移两个数据单位,表示第二行第三个元素的地址
printf("%d\n",**p+2); //14 **p=12, **p+2=14
printf("%d\n",*(*p+1)); //90 *p是第一个元素的地址,向后偏移一个字节得到第二个元素的地址
printf("%p\n",p+2); //p是一个行指针,指向第一行元素,p+2指向第三行元素,访问越界
printf("%d\n",*p[2]); //先执行p[2]<==>*(p+2) *p[2]<==>**(p+2) 行指针p向后偏移2行本身就是非法访问
return 0;
}
2.行地址和列地址
行地址:
二维数组的数组名和数组指针都是行地址,偏移量为一行元素
列地址:
一维数组的数组名和一级指针都是列地址,偏移量为一个数据单位
3.指针数组
本质是一个数组,保存的是多个指针
#include <stdio.h>
int main(int argc, const char *argv[])
{
//如果想要用二维数组存储多个字符串时,列数需要和最长的字符串一致,会造成空间的浪费
char str[][20]={"hello","hhhhhhhhhhhhhhhhhh","i"};
printf("%ld\n",sizeof(str));
//因为字符串会被在字符串常量区创建,所以可以直接用指针指向字符串常量,使用指针数组存储多个指针
char *arr[3]={"hello","hhhhhhhhhhhhhhhhhh","i"};
puts(arr[1]); //通过指针数组输出
printf("arr[1]=%p\n",arr[1]);
printf("2\n");
arr[1]="world"; //改变指针数组中第二个指针的指向
printf("arr[1]=%p\n",arr[1]);
printf("1\n");
printf("%c\n",*arr[1]);
*arr[1]='a'; //arr[1]是字符串常量区中字符串world的首地址]
//*arr[1] 对world的首地址取*
r
4.main函数的外部传参
rgc:表示外部参数的个数
argv[]:是一个指针数组,保存多个字符类型的指针,每个指针都指向一个外部参数
主函数如何进行外部传参:
./a.out 参数1 参数2 参数3···
(./a.out是第一个参数,后续的每一个参数中间以空格分隔)
5.二级指针
数据类型 **指针变量名;
保存一级指针的地址的
多级指针一定是指向上一级指针的,所以偏移量是固定的,
64位操作系统偏移量8Byte,32位操作系统偏移量4Byte
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a=90;
int *p=&a;
printf("%ld\n",sizeof(p)); //8
//int *p1=&p; 一级指针的偏移量是一个基本数据类型的大小,
//如果使用一级指针保存另一个一级指针的地址,不能保证偏移时偏移整个指针的空间
//printf("p1=%p\tp1+1=%p\n",p1,p1+1);
//需要使用二级指\针保存一级指针的地址
int **p2=&p;
printf("p2=%p\tp2+1=%p\n",p2,p2+1);
return 0;
}
二、函数部分
函数是实现某些功能的代码
1.定义
返回值类型 函数名(参数列表)
{
函数体;
return 返回值; //如果返回值类型是void,return可以不写
}
返回值:返回给主调函数处的结果,如果主调函数处不需要结果就不用写返回值(如果函数没有返回值,就写void)
参数列表:如果实现函数功能需要外部传递参数,就需要写参数列表,如果实现函数功能时不需要外部传递参数,不需要写参数列表
2.函数的分类
i)无参无返回值函数
void func()
{
printf("hello world\n");
}
ii)有参无返回值函数
void func(int a,int b)
{
printf("%d\n",a+b);
}
iii)无参有返回值函数
int func()
{
int c;
scanf("%d",&c);
return c;
}
iv)有参有返回值函数
int func(int a,int b)
{
return a+b;
}
3.简单函数的实现
#include <stdio.h>
//自定义函数
int add(int a,int b)
{
return a+b;
}
int main(int argc, const char *argv[])
{
int x=9,y=3;
printf("%d\n",add(x,y));
int ret = add(x,y); //因为add函数有返回值且为int类型
//所以可以直接使用int类型的变量接收函数的结果
return 0;
}
4.函数声明
函数声明的作用:引导编译器正确找到调用的函数,声明了函数的返回值类型和参数类型
函数声明的时机:函数定义(实现)放在了函数调用的后面(和主调函数不在同一个文件)需要用到函数声明
5.函数的值传递和地址传递
自己定义函数实现两个数的交换
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a=12,b=34;
printf("a=%d\tb=%d\n",a,b);
//用算数求和的方式
a=a+b; //46
b=a-b; //12
a=a-b; //34
printf("交换后\n");
printf("a=%d\tb=%d\n",a,b);
//用异或的方式交换两个数
a=a^b;
b=a^b;
a=a^b;
printf("交换后\n");
printf("a=%d\tb=%d\n",a,b);
//a=a^b
//0110 ^ 0011 = 0101
//b=a^b
//0101 ^ 0110 = 0011
//a=a^b
//0101 ^ 0011 = 0110
return 0;
}