C语言之指针
C语言指针
此部分的内容可以在YouTube中up主Feis Studio(李根逸博士)的视频中有讲解
知识点1[类型]
基本类型:
char
short
double
float
int
long
char 系统在内存中开辟1个字节的空间
int 系统在内存中开辟4个字节的空间
short 系统在内存中开辟2个字节的空间
指针:
对内存中的每一个字节分配一个32位或者64位的编号(与32位和64位编译器相关)
这个编号称之为内存地址,指针的实质为内存“地址”。
注意:由于不同类型的变量占用的字节数不同,如
int
占用四个字节,此时指针指向地址为首地址
知识点2
指针的定义用符号 *
取变量的地址用符号&
在使用指针中*
代表去指针指向地址中的值
指针变量两种类型:自身的类型 指向的类型
自身的类型:在指针变量定义的时候,将变量名去除,剩下的是什么类型 指针变量就是什么类型,如
int *p = &a
中指针变量的类型为int *
;再如int **p = &a
中指针变量的类型为int **
int *
类型的变量可以存放一个int数据的地址指向的类型:在指针变量定义的时候,将变量名和离他最近的一个
*
一起去除,剩下的是什么类型,指针变量指向的类型就是什么,如int *p = &a
中指针变量指向的类型为int
;再如int **p = &a
中指针变量指向的类型为int *
指针变量指向的类型的作用:决定了指针变量所取空间内容的宽度 决定了指针变量
+1
操作后跳过的单位跨度
C语言可以进行指针的减法(相差的元素个数),但是不能进行指针的加法(会报错)
int a = 100; //定义变量a
int *p = &a; //定义指针 指针大小是8字节(电脑是64位)
char b = 's';
char *pb = &b; //定义指针,指针大小是8字节(电脑是64位)
//指针变量+1的跨度测试
printf("&a[0]= %lu \n", p); //输出为140732481637536
printf("p+1= %lu \n", p+1); //输出为140732481637536 + 4 = 140732481637540
printf("&b[0]= %lu \n", pb); //输出为140732481637525
printf("pb+1= %lu \n", pb+1); //输出为140732481637525 + 1 =140732481637526
//指向的类型也决定了指针变量所取空间内容的宽度
int num = 0x01020304;
int *p1 = # //4个字节
short *p2 = # //2个字节
char *p3 = # //1个字节
printf("*p1 = %#x \n",*p1); //输出0x01020304
printf("*p2 = %#x \n",*p2); //输出0x0304
printf("*p3 = %#x \n",*p3); //输出0x04
printf("*p2 + 1 = %#x \n",*(p2+1)); //输出0x01
return 0;
知识点3
大端模式和小端模式
- Little - Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。个人PC常用,Intel X86处理器是小端模式。
- Big - Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
采用大端方式 进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。有些机器同时支持大端和小端模式,通过配置来设定实际的端模式。
假如 short类型占用2个字节,且存储的地址为
0x30
short a = 1;
如下表所示:
地址 | 0x30 | 0x31 |
---|---|---|
小端模式 | 00000001 | 00000000 |
大端模式 | 00000000 | 00000001 |
指针与数组
数组与指针本质上是相同的
int v[5];
int *n;
n = &v[0];
n = v; //等效与第三行,数组和指针是类似的
n + 1 == &v[1]; *(n+1) == v[1];
n + 2 == &v[2]; *(n+2) == v[2];
循环存取数组的元素
#include
int main() {
int v[5] = {1, 2, 3, 4, 5};
int *n = v;
int i;
for(i = 0; i < 5; i++){
printf("%d\n", *(n+i)); //最后 n还是等于&v[0]
}
return 0;
}
上述函数可以修改为:
#include
int main() {
int v[5] = {1, 2, 3, 4, 5};
int *n;
for(n = v; n != &v[5]; n++){ //利用指针后移的方法,最后n=&v[5],超出原有数组一个。v[5]元素不存在,但是v[5]的地址是存在的。
printf("%d\n", *n);
}
return 0;
}
指针与数组下标索引
[]
是一种运算符,a[b]==*(a+b)
int v[5];
int *n = v;
n[0] = 0; // a[b] 就是*(a + b) 此运算是不论a 和 b是什么的, 可以看第六行等式
n[0] == v[0];
0[v] == n[0] == v[0];
int maxv(int[3]);
int main(){
int a[3] = {3,9,7};
printf("Max: %d\n",maxv(a));
return 0;
}
int maxv(int v[3]){ //在此处,其实C语言编译器的处理为指针,即 int maxv(int *v),根本在于数组的复制是需要利用for循环完成的,此处数组并没有完成复制的工作,只是给了首地址。
int max = v[0],i;
for (i = 1; i < 3; i++){
if (v[i] > max){
max = v[i];
}
}
return max;
}
故当数组作为函数的传入接口时,通常会有另一个接口用以限定数组的大小。
int maxv(int *, int N);
int main(){
int a[3] = {3,9,7};
printf("Max: %d\n",maxv(a,3));
int b[5] = {3,9,1,2,7};
printf("Max: %d\n",maxv(b,5));
return 0;
}
int maxv(int *v, int N){ //在此处,其实C语言编译器的处理为指针,即 int maxv(int *v),根本在于数组的复制是需要利用for循环完成的,此处数组并没有完成复制的工作,只是给了首地址。
int max = v[0],i;
for (i = 1; i < N; i++){
if (v[i] > max){
max = v[i];
}
}
return max;
}
指针与字符串
字符串与指针的特殊性
字符串是无法直接复制的,
字符串的""
和{}
是有区别的,可以自己转为隐性的指针
字符串储存的时候,是用字符数组的形式进行存储的
char strA[] = "test";
char strB[] = {'t','e','s','t','\0'}
printf(strA);
printf(strA); //通过\0 来判断结束
printf("test");
printf({'t','e','s','t','\0'});// 语法错误
char strA[] = "test"; //可读可写
char *strB = "test"; //
strA[0] = 'T'; // 可以
strB[0] = 'T'; // 不可以
strA = "Test"; //不可以
strB = "Test"; //可以
const
修饰字
被const
修饰的变量在初始化之外是不能再被赋值的
可以理解为只读属性(OR,only-read)
int a = 3;
const int b = 5; //b只能读
const int c; //未初始化,错误
char strA[] = "test"; //可读可写
char *strB = "test"; //等效为 const char *strB = "test";
strA[0] = 'T'; // 可以
strB[0] = 'T'; // 不可以
strA = "Test"; //不可以
strB = "Test"; //可以
function
printf
int printf ( const char * format, ···);
使用函数复制字符串
字符串也是字符数组,不能被直接复制
int main(){
char source[5] = "test";
char destination[5];
destination = source; // 会出现编译失败,字符数组是不能直接复制的
printf("%s\n",destination);
return 0;
}
int main(){
char source[5] = "test";
char *destination;
destination = source; // 浅复制(shallow copy)和原来就是同一个,并没有复制成功
//深复制(deep copy)
for(int i = 0; i < 5; i++){
destination[i] = source[i];
}
printf("%s\n",destination);
return 0;
}
function
char * strcpy(char *destination, const char * source);
copy string
//使用函数对字符串进行复制(错误案例)
int main(){
const char *source = "test";
char *destination; //这是不可写的,具体参照上文中的第二标题,即该语句其实内含有const,正确的应该是 `char destination[5];`
strcpy(destination, source);
printf("%s\n",destination);
return 0;
}
指针数组
数组中的元素均为指针时,称为指针数组。
int *p[3];
int v[3] = {1,2,3};
int *p[3] = {&v[0],&v[1],&v[2]}; //指针数组
//循环存取
for(i = 0; i<3; i++){
*p[i] = 0; //数组清零, p[i]存储的是数组V[i]的地址,则*p[i] = *(*(p+i))
}
//指针数组可以实现定制化的位置
int v[3] = {1,2,3};
int *p[3] = {&v[2],&v[1],&v[0]}; //指针数组
//指针数组可以存储多个数组
int a[2] = {1,2};
int b[2] = {3,4};
int c[2] = {5,6};
int *p[3] = {a,b,c};
int i,j;
for ( i = 0; i< 3; i++){
for(j=0; j< 2; j++){
p[i][j] = 0;
}
}
//将一个二维数组存放在指针数组中
int *p[3];
int a[3][4];
for(i=0; i< 3; i++){
p[i] = a[1]; //在此种,p[0]中存放的是第一行的首地址即a[0],也即&a[0][0];p[1]中存放的是第二行的首地址即a[1],也即&a[1][0]
}
数组指针
需要注意和指针数组的区别,指针数组是一个存放指针的数组,数组指针是一个指向数组的指针
int v[3] = {1,2,3};
int (*q)[3] = &v; //q本身是一个指针,这个指针有三个元素,每一个都是int,注意和指针的区别
int i;
for (i = 0; i < 3; i++){
(*q)[i] = 0;
}
#include
void print(int (*q)[3]){ //q是一个指针,指向的是一个3个整形元素的数组,q代表的是整个数组的地址 可以获得长度信息
int i;
for (i = 0; i < 3; i++){
printf("%d ", (*q)[i]);
}
printf("\n");
}
int main(void){
int v[3] = {1,2,3};
print(&v);
return 0;
}
/////////////////////////////////////////////////////////////////////我是分界符
void print(int *q){ //q是一个指针指向的是一个整形元素,q代表的是第一个元素的地址 不能获得长度信息 此类方法更具有普遍适用性
int i;
for (i = 0; i < 3; i++){
printf("%d ", q[i]);
}
printf("\n");
}
int main(void){
int v[3] = {1,2,3};
print(v);
return 0;
}
注意赋值符号=
两边的数据类型是必须相等的,如果不相等,则需要进行显示或者隐式的类型转换
指针数组和数组指针的区别
指针数组:首先指针数组是一个数组,该数组的元素均为指针,数组占多少个字节,由元素的个数决定,在32位系统中任何元素的指针永远都是占4个字节,所以指针数组占用4*n个字节。它是“存储指针的数组”的简称
数组指针:首先它是一个指针,该指针指向一个数组,在32位系统中,所有指针都是占用4个字节,至于所指向的数组占用多少字节,具体要看数组大小和数组的类型。它是“指向数组的指针”的简称。
int *p1[10]; //指针数组
int (*p2)[10]; //数组指针
[]
的优先级比*
要高。p1 先与[]
结合,构成一个数组的定义,数组名为p1
,int
修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int
类型数据的指针,即指针数组。至于p2,在这里()
的优先级比[]
高,*
号和p2 构成一个指针的定义,指针变量名为p2,int
修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int
类型数据的数组,即数组指针。
注意数组指针的类型,如果对指针+1
操作,地址移动多少位?
具体可以参考博客:
int v[3] = {1,2,3}; //数组
int *n = v; //指针
int *p[3] = {&v[2],&v[1],&v[0]} //指针数组
int (*q)[3] = &v; //数组指针
int main(){
int v[3] = {1,2,3}; //数组
printf("%zu\n",sizeof(v)); //数组的大小 = 4*3 = 12
printf("%zu\n",sizeof(v[0])); //数组0的值 int = 4
printf("%zu\n",sizeof(&v[0])); //数组0的地址,指针, 64位计算机 8位
printf("%zu\n",sizeof(&v)); //数组的地址 ,指针, 64位计算机 8位
return 0;
}
绝大数情况下,指向不同类别的指标是不能直接隐性转型 176