@toc 面试
前言数组
在C语言基础阶段,咱们学习过指针相关的一些基础内容,好比说:markdown
1.指针是一个变量,用来存放地址,地址是惟一标识一块内存空间
2.指针的大小是固定的4 / 8个字节(32位平台 / 64位平台)
3.指针是由类型,指针的类型决定了指针的 + -整数的步长,指针解引用操做时候的权限
4.指针的运算数据结构
本篇文章及后面的几篇文章将会更加详细的去介绍和学习指针的进阶部分。(指针的内容在数据结构中会常常用到,因此必定要好好学习,打好基础~)ide
接下来,咱们继续探讨指针的高级使用函数
在指针的类型中咱们知道有一种指针类型为字符指针 char*
通常使用方式:学习
还有使用方式以下:设计
注意观察区别:%C 与 %S :3d
这种方式是将字符串的首地址放到指针中,经过指针能够找到该字符串(千万不要理解成将字符串放到指针里面去,这是不可能的)
。(相似与数组名就是首元素地址,可是跟数组仍是有所区别的,这个字符串是一个常量字符串,没法被改变,以下图:)指针
常量字符串不能改变
若是说咱们想修改这个字符串,须要将其放入数组中,而后再去修改:
扩展:在C语言中,内存能够被划分为栈区、堆区、静态区、常量区。
栈区:局部变量,函数形参,函数调用
堆区:动态内存如malloc等申请使用
静态区:全局变量,static修饰的局部变量
常量区:常量字符串
常量区中的内容在整个程序的执行期间是不容许被修改的,且同一份常量字符串只会建立一份,不会重复建立存储。
看一面试题,输出什么?
#include <stdio.h> int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; char *str3 = "hello bit."; char *str4 = "hello bit."; if(str1 ==str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if(str3 ==str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
结果展现:
const修饰常量
分析:
建立数组须要开辟空间,数组arr1和arr2在内存空间所在位置是不一样的,因此arr1 != arr2;
char p1 = “abcdef”; char p2 = “abcdef”; "abcdef"是常量字符串,不能被修改,在内存空间所占位置固定,char p1 = “abcdef”; 是将该常量字符串的首地址放到字符指针p1中,char p2 = “abcdef”;
是将该常量字符串的首地址放到字符指针p2中。
也就是说p1和p2存放都是常量字符串"abcdef"的首地址,因此p1 ==p2
。(注意:一样的常量字符串只会存一份,不会同时存两份,因此不会开辟不一样的空间来存储。)
总结:
这里arr1和arr2指向的是一个同一个常量字符串。
C /C++会把常量字符串存储到单独的一个内存区域当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。可是用相同的常量字符串去初始化不一样的数组的时候就会开辟出不一样的内存块。因此arr1和arr2不一样,p1和p2相同。
经过前面的学习,咱们能够知道形如int arr1[5] = {0};的是整形数组,数组存放的是整形,形如char arr2[10] = {0};的是字符数组,数组存放的是字符。
同理,指针数组应该是数组,数组存放的是指针。
//指针数组 - 数组 - 存放指针的数组 int* arr[4];//存放 整形指针的数组 - 指针数组 char* ch[5];//存放 字符指针的数组 - 指针数组
指针数组的简单使用
看如下代码,猜猜结果是什么?
//指针数组的简单使用 int main() { int a = 10; int b = 20; int c = 30; int d = 40; int* arr[4] = {&a, &b, &c, &d}; int i = 0; for (i = 0; i < 4; i++) { printf( "%d ", *(arr[i]) ); } return 0; }
咱们知道数组名能够表明首元素的地址,请看下面这段代码:
#include<stdio.h> int main() { int arr1[] = { 1,2,3,4,5 }; //arr1--int* int arr2[] = { 2,3,4,5,6 }; //arr2--int* int arr3[] = { 3,4,5,6,7 }; //arr3--int* int* parr[] = { arr1,arr2,arr3 }; //存入每一个数组首元素数组 //经过arr数组打印arr1,arr2,arr3三个数组中的元素 int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *( (parr[i] + j) ); } printf("\n"); }
上面举例都是用整型指针数组,接下来咱们来看一个字符指针数组的例子:
#include<stdio.h> int main() { char* p1 = "student_zhang"; char* p2 = "guofucheng"; char* p3 = "liudehua"; char* ch[3] = { p1,p2,p3 }; int i = 0; for (i = 0; i < 3; i++) { printf("%s\n", ch[i]); } return 0; }
这里咱们再复习一下,下面指针数组是什么意思 ?
int* arr1[10]; //整型指针的数组 char* ch[5]; //字符指针的数组 char* arr2[4]; // 一级字符指针的数组 char** arr3[5]; //二级字符指针的数组
数组指针是指针仍是数组 ?
答案是∶指针
咱们已经熟悉︰
整形指针 : int p ; 可以指向整形数据的指针。
浮点型指针 : float pf ; 可以指向浮点型数据的指针。
那数组指针应该是︰可以指向数组的指针。数组指针和指针数组要区分开来。
整型指针 ---> 指向整型的指针
int a = 10; int* pa = &a;
字符指针 ---> 指向字符的指针
char ch = 'w'; char* pc = &ch;
数组指针---> 指向数组的指针----->eg : int (*p)[10]=NAll ;
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //int* p = arr;//数组名是首元素地址 //数组指针 存放数组的指针变量 int(*p)[10] = &arr;//(*p)表明p是指针变量 //该指针指向了一个数组,数组10个元素,每一个元素的类型是int //若是不用括号将*p括起来,写成int* p[10],这是指针数组 return 0; }
首先,咱们要知道[]的优先级是比 要高的,对于形式1,p1会先与[ ]结合,在与 结合,因此形式1是指针数组,()的优先级又比[]高,因此p2会先于 * 结合,在与[ ]结合,因此形式2是数组指针。
到这里,相信你对数组指针有了必定了解,哪么怎么创建数组指针呢
?
✳
例如:给定char arr[5],请写出用来表达char arr[5]的数组指针
先分析char arr[5],很明显,这是一个指针数组,数组名是arr,有五个元素,数组的类型是char,咱们已知数组指针 - 指向数组的指针 - 存放数组的地址,因此应该对数组取地址,即&arr,如何应该定义一个有五个元素的指针存放数组的地址,即(p)[5],指针类型为char,因此数组指针是char (p)[5] = &arr
不成熟的说指针的指针至关于二级指针,确定有两个 “ * ”
因此咱们如今会写数组指针了。
来试一试!
例如:
char( pa)[10] = &arr
int( pa)[2] = &par
咱们看到打印的结果都是同样的,那么数组名arr和数组的地址 & arr是同样的吗?
从地址值来看,二者是同样的,可是二者的含义和使用是不一样的:
int p1; p1+1 表示跳过一个int类型的长度,也就是4个字节
char p2; p2+1表示跳过一个char类型的长度,也就是1个字节
int(p3)[10]; p3+1表示跳过一个具备10个整型长度的数组,也就是410=40个字节
咱们先看这个例子:
经过数组指针解引用找到数组,再用方括号[ ],去找到数组中的每一个元素。
这种并不是数组指针的经常使用方式,由于用起来很“别扭”。
这种方式不如首元素地址 + i 流畅:
数组指针的使用,通常常见于二维数组及其以上
当咱们在谈首元素的时候,一维数组的首元素就是第一个元素,二维数组的首元素要先将二维数组看做一维数组(该数组中每个元素都是一个一维数组),那二维数组的首元素就是第一个一维数组。那么二维数组的首元素地址就是第一个一维数组的地址!(不是第一个一维数组中第一个元素的地址,虽然值相同,但含义和使用不一样)
#include<stdio.h> //常见的方式 void print_arr1(int arr[3][5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } //数组指针方式 void print_arr2(int(*p)[5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", (*(p + i))[j]); printf("%d ", *(*p + i)+j); printf("%d ", *(p[i]+j); printf("%d ", p[i][j]); //四个都等价 } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; //打印这个二维数组 print_arr1(arr,3,5);//数组名,行,列也要传参 print_arr2(arr, 3, 5); //数组名-首元素地址 return 0; }
结果展现:
图解:
注意:对一个存放数组地址的指针进行解引用操做,找到的是这个数组,也就是这个数组的数组名,数组名这时候又表示数组首元素地址!
*( p + i ):至关于拿到了一行<br/>至关于这一行的数组名<br/>( *p + i )[ j ] <===> *(*(p + i ) + j )
为了更好的理解这一点,咱们来看这个例子:
#include<stdio.h> int main() { 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)); //方式一:经过指针找到与首元素偏移i个元素的地址, //再对齐解引用操做,找到这个元素 printf("%d ", *(arr + i)); //方式二:既然能够将arr赋值给p,说明arr与p等价 //那么就能够直接用arr替代p进行相应的解引用操做 printf("%d ", arr[i]); //方式三:经过数组名+[下标]访问数组元素 //即arr+[下标i]访问下标为i的元素,也就是第i+1个元素 printf("%d ", p[i]); //方式四:既然arr与p等价, //那么也能够直接用p+[下标]的方式访问数组的元素 //上述四种方式实际结果彻底相同,实际上也能够互相转换使用 } return 0; }
恍然大悟
总结:咱们对一个数组指针变量进行解引用操做,好比int(*p)[10],获得的是一个数组,或者说是这个数组的数组名,而数组名又能够表示该数组首元素的地址。若是要找到该数组中的每个元素,就须要对这个数组元素的地址进行解引用操做。
简单点来讲就是,对一个数组指针类型进行解引用操做,获得的仍是地址,对这个地址在进行相应的解引用操做,才能获得数组中的具体的元素。
下面这些代码的含义是什么?
int arr[5]; int* parr1[10]; int(*parr2)[10]; int(*parr3[10])[5];
解析:
int arr[5]; //arr是一个数组,数组有5个元素,每一个元素类型是int //arr类型是 int [5] --- 去掉变量名,剩下的就是变量的类型 int* parr1[10]; //parr1是一个数组,数组有10个元素,每一个元素类型是int* //parr1是指针数组,类型是 int* [10] int(*parr2)[10]; //parr2是一个指针,指针指向一个数组,数组有10个元素,每一个元素的类型是int //parr2是数组指针,类型是 int(*)[10] int(*parr3[10])[5]; //parr3是一个数组,数组有10个元素,每一个元素都是一个指 针 //指针指向一个数组,数组有5个元素,每一个元素类型是int //parr3是一个指向数组指针的数组,本质上仍是数组 //parr3类型是 int(*[10])[5]
int( parr3 [10])[ 5 ]; 拿掉数组名后,剩下 int()[5]就是这个数组的类型
问题1:parr2 = &parr1;//可否将数组parr1的地址放到parr2中呢?
答:不能,由于类型不匹配,parr2指向的类型应该是 int[10] parr1 是 int* [10];
#include <stdio.h> void test(int arr[])//ok? {} void test(int arr[10]) //ok? {} void test(int* arr)//ok? {} void test2(int* arr[20])//ok? {} void test2(int** arr) // ok ? {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
答案:以上五种传参方式均ok
注意:一维数组传参能够传数组形式,也能够传指针形式,传数组形式的时候数组元素的个数能够不写,也能够写,传指针的时候要注意指针的类型,也就是指针指向什么类型的元素,
好比说指针指向int类型元素,那么指针的类型就是 int*
void test(int arr[3][5])//ok ? {} //能够 void test(int arr[][])//ok ? {} //不能够,行能够省略,列不能够,第一个[ ]内容能够不写,第二个[ ]要写 void test(int arr[][5])//ok ? {} //能够 void test(int* arr)// ok ? {} //不能够 void test(int* arr[5])//ok ? {} //不能够 void test(int(*arr)[5])//ok ? {} //能够 void test(int** arr)// ok ? {} //不能够 int main() { int arr[3][5] = { 0 }; test(arr); }
总结 : 二维数组传参,函数形参的设计只能省略第一个[ ]的数字。
由于对一个二维数组,能够不知道有多少行,可是必须知道一行多少元素。
这样才方便运算。
二维数组传参也能写成指针的形式,指针的类型应该是数组指针。
思考1:这里的指针传参能够用数组去接收吗?
通过实验咱们能够看到这样作是没问题的,指针传参能够用数组去接收!
思考2:当一个函数的参数部分为一级指针的时候,函数能接收什么参数 ?
例如:int * p
void test1(int* p) { } int main() { int a = 10; int* pa = &a; int arr[10] = { 0 }; test1(&a); test1(pa); test1(arr); return 0; }
再例如:char* p
void test2(char* p) { } int main() { char a = 'W'; char* pa = &a; char arr[10] = { 0 }; test2(&a); test2(pa); test2(arr); return 0; }
注意:
int p1;
int p2;
*靠近int 或者靠近变量p实际的意义和效果没区别,是同样的。 通常咱们习惯用int p2这种写法,这样能够明确表示指针变量p2的类型。**
void test(int** p) { } int main() { int* p1; int** ptr; int* arr[5]; test(&p1);//一级指针取地址 test(ptr);//二级指针 test2(arr);//一级指针数组的首元素 return 0; }