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

大端模式和小端模式

  1. Little - Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。个人PC常用,Intel X86处理器是小端模式。
  1. 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 先与[]结合,构成一个数组的定义,数组名为p1int 修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含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