有了这份指针基础概念归纳,相信你能够理解指针(8)

学好了指针可能才是真正的给C语言这把剑开了刃

指针的概念复杂,但却有趣

这两周在学习指针,也是从很多的课程和课本去了解它,重新复习了《C程序设计》之中关于指针的知识内容,并且开始阅读《C和指针》这本书,看到它将近400页的内容,说实话有点怂了,这可能和我们模电,数电,信号处理的课本有着一比,让人看见了就怕哈哈。
指针可能相对于其他概念来说,应该算是C语言之中较为难学的一个部分了,但是在接触到指针之后还是被它那种特殊的方式给征服了,这种通过地址而改变变量,进行赋值,进行运算的方式可能和我们正常生活之中所理解的思维是不同的,所以对于初学者来说,我们应该更多的通过代码里的内容来进行更加深入的理解,这样才能够更好的学习好对于指针的理解。

指针概念和相关的程序说明

  1. 一个变量的地址,称为该变量的“指针”,可以在定义指针的同时,对他们进行初始化。
int a=10;
int* p=&a;//将a的地址赋给p;
  1. C语言的每种指针类型或者说是数据类型占据多少个字节,取决于它的编译器
    1. 32位系统上,指针占据4个字节 所支持的最大内存是4G;
    2. 64位系统上,指针针具8个字节。
  2. 指针变量的写法:建议将指针变量写成int* p的样子,因为在C语言之中int* p时一种单独的数据类型,和int double float都是并列关系。
  3. 对于指针类型的理解:
    1. 内存中的房间号(地址)是什么,也就是说指针变量中所存放的数据;
    2. 对应的房间号房间有多大(指针的类型则式地址所对应的内存多大)。
  4. &取地址运算符:&a是变量a的地址;
    *指针运算符:*p代表指针变量p所指向的对象;
  5. 在C语言之中,实参变量和形参变量之间的数据传递时单向的“值传递”,所以如果想要通过函数调用的形式得到几个要改变的值,则需要:
    1. 在主函数中设n个变量,用n个指针变量指向它们;
    2. 设计一个函数,有n个指针形参,在这个函数中给改变这n个形参的值;
    3. 在主函数中调用这个函数,在调用的时候,将这n个指针变量作实参,将它们的地址传给该函数的形参;
    4. 执行该函数,通过形参指针变量,改变他们所指向的n个变量的值;
    5. 主函数中就可以使用到这些改变了值得变量了。
  6. [] 实际上时变址得运算符,即将a[i],按照a+i进行计算地址,然后找出此地址单元之中的值;
  7. 指针数组是一个数组,数组之中的每个元素又是一个指针变量 int* arr[4]={0};
  8. 数组指针是一个指针, int(*arr)[4]=NULL;
**指向数组的指针作为函数参数**
#include <stdio.h>
#include <stdlib.h>

void average(float* p, int n){
	float* p_end;
	float sum = 0, aver;
	p_end = p + n - 1;//P_end指向最后一个元素
	for (; p <= p_end; p++){
		sum +=(*p);
	}
	aver = sum / n;
	printf("average=%5.2f\n", aver);
}

void search(float(*p)[4], int n){//p指向具有四个元素的一维数组的指针
	int i;
	printf("The score of No.%d are :\n");
	for (i = 0; i < 4; i++){
		printf("%5.2f  ", *(*(p + n) + i));//
	}
	printf("\n");
}

int main(){
	float score[3][4] = { { 65, 67, 70, 60 }, { 80, 87, 90, 81 }, { 90, 99, 100, 98 } };
	average(*score, 12);//求十二个数的平均分
	search(score, 2);//求序号为2的学生成绩
	system("pause");
	return 0;
}

  1. 数组作为函数参数的话: 惊天BUG!!!
    1. 就会隐式转为指针变量,将会指向数组的首元素地址;
    2. 数组名直接printf的时候也会隐式转为指针;
    3. 数组名参与算术运算也会隐式转成指针。
  2. 当用变量名作为函数参数时,所传递的时变量的值,当用数组名作函数参数时,由于数组名代表的时数组首元素地址,因此传递的时地址,所以要求形参必须为指针变量。
  3. 函数的形参和实参之间的关系:
    1. 形参是实参的一份拷贝
    2. 单纯用用的话传数字就可以,而如果想要进行修改的话就必须传递地址;
    3. 想要返回多个值,则需要进行地址的传,也就是参数传指针的方式。
    4. 参数传指针的方式,是可以让一个函数内部修改函数外部的变量,这样的参数页被人们称为“输出型参数”。
  4. int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};指针的集中形式
  5. 字符数组中个元素的值是可以改变的,但是字符指针变量指向的字符串常量中的内容是不可以被取代的。
  6. 定义和使用指向函数的指针变量: 类型名( 指针变量名)(函数列表)*。
**指向函数的指针变量**
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int Max(int x, int y){
	int z;
	if (x > y){
		z = x;
	}
	else {
		z = y;
	}
	return (z);
}

int Min(int x, int y){
	int z;
	if (x < y){
		z = x;
	}
	else {
		z = y;
	}
	return (z);
}

int main(){
	int(*p)(int, int);//定义指向函数的指针变量
	int a, b, c, n;
	printf("please enter a and b:");
	scanf("%d,%d", &a, &b);
	printf("please choose 1 or 2");
	scanf("%d", &n);
	if (n == 1){
		p = Max;
	}
	else if(n==2){
		p = Min;
	}
	c = (*p)(a, b);//调用p指向的函数
	printf("a=%d,b=%d\n", a, b);
	if (n == 1){
		printf("max=%d\n", c);
	}
	else{
		printf("min=%d\n", c);
	}
	system("pause");
	return 0;
}

  1. 字符串的比较:
    1. 使用 strcmp 函数来进行比较(比较字符串的内容,里面的每个字符是不是依次相同)
    2. ==比较字符串的身份/地址 (是不是同一个字符串变量);
  2. 指针比较相等的话,规则就是比较两个指针之中存在的地址是不是一样的。
  3. 指针数组的元素只能够存放地址,不能够存放整数。
  4. int main(int argc ,char *argv) ,其中argc代表参数的个数,而argv表示参数向量。
  5. 动态内存:
    1. malloc(100)开辟100字节的临时分配域,函数值为其第一个字节的地址;
    2. p=callor(50,4)开辟50x4个字节临时变量,把起始地址赋给指针变量p;
    3. free(p) 释放指针变量p所指向的已经分配的动态空间;
    4. realloc(p,50)将p所指向的已经分配好的动态空间改为50字节。
  6. 当把void指针赋值给不同类型的指针变量时,编译系统会自动进行转换,不必用户自己进行强制转换
p=&a; 
p=(viod*)&a;//p3所得到的a纯地址
两者等价!!!
  1. 解引用也被称之为间接访问操作,是根据指针变量之中保存的地址,来找对应的内存中的值,千万不能对对非法内存进行解引用,这样的话会导致“未定义行为”,内存申请到的才能够进行使用,没用申请到的是不能够使用的(也就是没有创建这个变量)。
  2. 包含一个非法地址的指针,被称为“野指针”,对于野指针进行解引用的话是非常危险的。
  3. 对于指针变量涉及的几个要素:
    1. 指针对应的内存地址是哪个地址,这是指针值的体现;
    2. 这个内存地址对应的内存大小是多大,指针的雷西那个体现;
    3. void* 这种特殊的指针,只有地址,没用内存对应的大小,并且它不能够被解引用,但是它的作用很大,当某个函数不需要关注类型的时候,或者说是需要支持多种指针类型的时候,都可以使用它,比如初始化函数memset
  4. 指针+整数:
  5. 指针+1:相当于地址向后跳过一个元素;
  6. 指针- 1:相当于地址向前跳过一个元素。
**指针+整数**
#include <stdio.h>
#include <stdlib.h>
int main(){
	double* p = (double*)0x100;//地址
	printf("%p\n", p + 1);//指针+1 则是地址加1
	system("pause");
	return 0;
}

**数组指针的整体+1**
#include <stdio.h>
#include <stdlib.h>

int main(){
	int arr[4] = { 1, 2, 3, 4 };
	int(*p)[4] = &arr;//数组指针
	printf("%p\n", &arr[0]);//数组指针的首地址
	printf("%p\n", arr+1);//数组指针整体加一
	printf("%p\n", &arr+1);//数组指针向后跳一
	system("pause");
	return 0;
}

  1. 指针相减的本指就是两个指针之间相隔了几个元素,其实是指针加减整数的逆运算。
  2. 指针相减要有意义,必须满足这两个条件:
    1. 两个指针的类型必须相同
    2. 两个指针必须指向同一个连续的内存空间
**两个指针相减**
#include <stdio.h>
#include <stdlib.h>
int main(){
	int arr[4] = { 1, 2, 3, 4 };
	int* p1 = &arr[0];
	int* p2 = &arr[2];
	int ret = p2 - p1;
	printf("%p\n%p\n", p1,p2);//打印两个指针的地址

	printf("ret=%d\n", ret);//打印两个指针相减之差

	system("pause");
	return 0;
}

  1. 对于void* 不能够进行加减证书的运算,也不能够进行指针的相减。
  2. 限制指针
  3. 二维指针的实例引用:
1. **逆序输出三个单词**
#include <stdio.h>
#include <stdlib.h>

int main(){
	char *name[] = { "I", "like", "Beijing." };
	char **p;
	int i;
	for (i = 2; i >= 0; i--){
		p = name + i;
		printf("%s ", *p);
	}
	system("pause");
	return 0;
}

  1. 指针之中printf的多种使用
#include <stdio.h>
#include <stdlib.h>

int main(){
	int arr[] = { 1, 2, 3, 4 };
	printf("%d\n", sizeof(arr));// 整个数组的大小
	printf("%d\n", sizeof(arr+0));//隐式转换为指针 ,表示第一个元素的大小
	printf("%d\n", sizeof(*arr));// 数组指针,全部为4
	printf("%d\n", sizeof(arr+1));// 同2一样
	printf("%d\n", sizeof(&arr));//取arr的地址,则也为数组指针
	printf("%d\n", sizeof(*&arr));//16  数组指针的解引用
	printf("%d\n", sizeof(&*arr));//4  针对int来取&
	printf("%d\n", sizeof(&arr+1));//4数组指针+1
	printf("%d\n", sizeof(&arr[0]));//4 int*
	system("pause");
	return 0;
}

**第一组**

char[6]
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("%d\n", sizeof(arr)); // 6 整个数组的内存大小
printf("%d\n", sizeof(arr + 0));  // 4 arr + 0 已经变成指针了
printf("%d\n", sizeof(*arr));// 1 *arr 得到一个 char
printf("%d\n", sizeof(arr[1]));// 1 arr[1] 得到一个 char
// &arr 类型是 char(*)[6]
printf("%d\n", sizeof(&arr));// 4 &arr 得到的是一个数组指针, 也是指针
printf("%d\n", sizeof(&arr[0] + 1)); // 4 得到的是一个 char*
//strlen 是求字符串的长度, 一个带 \0 的字符数组才叫字符串
printf("%d\n", strlen(arr));  // 未定义行为
printf("%d\n", strlen(arr + 0)); // 未定义行为
// 原则上应该要编译失败, 但是 C 语言对于类型检查没那么严格,
//就导致把 char 隐式转成了 char*. 这样还是得到了一个非法的指针. 解引用的时候就会出现未定义行为
printf("%d\n", strlen(*arr));  //这个代码同上
printf("%d\n", strlen(arr[1]));
char(*)[6];
// &arr 类型和 char* 不一致. 如果隐式类型转换过去的话, 就会得到一个指向 'a' 指针. 从这个指针开始往后找 \0 仍然找不到
printf("%d\n", strlen(&arr)); //这个代码和上面一样
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1)); // 得到一个指向 b 的指针. 但是仍然是未定义行为


**第二组**

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));	// 7 带 \0 
printf("%d\n", sizeof(arr + 0));// 4 arr + 0 得到一个 char*
printf("%d\n", sizeof(*arr));// 1 *arr 得到一个字符
printf("%d\n", sizeof(arr[1]));// 1 同上
printf("%d\n", sizeof(&arr));// 4 &arr 数组指针
printf("%d\n", sizeof(&arr + 1));// 4 同上
printf("%d\n", sizeof(&arr[0] + 1));// 4 得到一个 char*
printf("%d\n", strlen(arr));//6 求字符串长度, 不算 \0
printf("%d\n", strlen(arr + 0));// 6 得到的还是一个指向 a 的指针
printf("%d\n", strlen(*arr));// 未定义行为, *arr 得到的是 字符 a
printf("%d\n", strlen(arr[1]));// 同上
printf("%d\n", strlen(&arr));// 6 &arr 是一个数组指针, 里面存的地址恰好就是数组首元素地址
printf("%d\n", strlen(&arr + 1));// 未定义行为, &arr 是一个数组指针, + 1 跳过了整个数组, 此时就已经访问的是非法内存了
printf("%d\n", strlen(&arr[0] + 1));// 5 &arr[0] + 1 得到指向 b 的指针


 **第三组**

char* p = "abcdef";
printf("%d\n", sizeof(p)); // 4
printf("%d\n", sizeof(p + 1)); // 4
printf("%d\n", sizeof(*p));// 1 *p 是一个 char 类型
printf("%d\n", sizeof(p[1]));// 1 p[1] 是一个char
printf("%d\n", sizeof(&p));// 4 char**
printf("%d\n", sizeof(&p + 1));// 4 char**
printf("%d\n", sizeof(&p[0] + 1));// 4, char* 指向 b
printf("%d\n", strlen(p)); // 6
printf("%d\n", strlen(p + 1)); //5 
printf("%d\n", strlen(*p)); // 未定义行为
printf("%d\n", strlen(p[1])); // 未定义行为
printf("%d\n", strlen(&p));// 未定义行为. &p => char** 
printf("%d\n", strlen(&p + 1)); // 未定义行为
printf("%d\n", strlen(&p[0] + 1)); // 5

这是到目前为止我所整理的一系列关于指针的相关概念,或许不是特别的全面,但是对于初学者来说,应该足够应付相关的程序和操作。

发布了18 篇原创文章 · 获赞 12 · 访问量 963

猜你喜欢

转载自blog.csdn.net/Luckily0818/article/details/103494591