一、C语言篇——07. 指针

01. 概述

指针变量:存地址的变量

内存单元:计算机中内存最小的存储单元。 —— 一个字节,每个内存单元都有唯一的编号,这个编程称为“地址”

02. 指针基础知识

  • 指针也是一种数据类型,占用内存空间,用来操作内存地址。
  • 指针变量指向谁,就把谁的地址赋值给指针变量
  • “*”操作符操作的是指针变量指向的内存空间
#include <stdio.h>

int main()
{
	//1. 基本认识
	int a = 0;
	printf("%p\n", &a); //%p 是打印a的地址,&a表示a在内存中的地址值

	//int *代表是一种数据类型,int*指针类型,p才是变量名
	int *p;  //定义了一个指针类型的变量,可以指向一个int类型变量的地址
	p = &a;  //将a的地址赋值给变量p
	printf("%d\n", *p);  //p指向了a的地址,*p就是a


	//2. 指针可以间接修改变量的值
	int aa = 0;
	int *pp = &aa;
	*pp = 100;
	printf("%d\n",aa);  //结果:100


	//3. 使用sizeof()测量指针的大小,sizeof()测的是指针变量指向存储地址的大小。
	//注意: 在32位平台,所有的指针(地址)都是32位(4字节)
	//     	在64位平台,所有的指针(地址)都是64位(8字节)
	int a1 = 0;
	int *p1 = &a1; 
	printf("%d\n",sizeof(p1));  //结果:4或者8


	//4. 指针也可以直接指向一个地址
	int* p2 = 0x1234;
	printf("p1 size:%d\n",sizeof(p2));


	//5.const修饰指针变量
	int a3 = 10;
	int b3 = 20;
	const int* p3 = &a3; //常量指针,指针指向的数据不能修改,指针的指向可以变
	//*p3 = 100; //错误,不可修改
	p3 = &b3;  //正确
	
	int* const p4 = &a3;  //指针常量,指针指向的数据可以修改,指针的指向不能变
	*p4 = 100;  //正确
	//p4 = &b3;  //错误

	const int* const p5 = &a3; //常量指针常量,指针指向的数据不能修改,指针的指向不能变
	//*p5 = 100;  //错误
	//p5 = &b3;  //错误

	return 0;
}

注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

03. 空指针、野指针、万能指针

空指针

空指针:指向NULL的指针
注意:不允许向NULL和非法地址拷贝内存

#include <stdio.h>
int main()
{
	int *p = NULL;  //空指针
	//*p = 10;  //错误

	int *pp = 0x1122; //直接指向地址
	//*pp = 20;  //错误,非法访问
	
	return 0;
}

野指针

野指针的产生:

  • 指针变量未初始化
  • 指针释放后未置空
  • 返回了局部变量的指针

野指针的避免:养成良好的编程习惯

#include <stdio.h>
int main()
{
	//1.未初始化
	int *p1;  

	//2.未置空
	int *p2 = (int*)malloc(sizeof(int)*2);
	free(p2);
	//p2 = NULL   //要置空

	//3.超越变量作用域
	int func();
	//int *p = func(); //错误
	
	return 0;
}

//返回局部变量的指针
int func()
{
    int a = 10;
    return &a;
}

万能指针

万能指针:void *万能指针可以指向任意变量的内存空间

#include <stdio.h>
int main()
{
	void *p = NULL; //万能指针

	int a = 10;
	//指向变量时,最好转换为void *
	p = (void *)&a; 

	//使用指针变量指向的内存时,转换为int *
	*( (int *)p ) = 11;
	printf("a = %d\n", a);
	
	return 0;
}

04. 指针和数组

注意:数组名就是 数组首元素的地址,是一个常量

#include <stdio.h>

int main()
{
    int a[] = {1,2,3};
    int b[3];
    
    //b = a; //错误,数组名是地址常量
    int *p = a;  //指针是变量。可以用数组名给指针赋值
    
    p[0] = 10;  //等价于 a[0] = 10;
    *(p+1) = 20;  //等价于 p[1] = 20;


	//指针数组,它是一个数组,数组的每个元素都是指针类型
	int a = 1;
	int b = 2;
	int c = 3;
	int *p[] = {&a,&b,&c};

	*(p[0]) = 10;  //a=10
	*(p[1]) = 20;  //b=20
	*(p[2]) = 30;  //c=30
    
    return 0;
}

05. 指针加减运算和指针步长

指针计算不是简单的数据相加减

指针运算:

  • 如果是一个int *,+1的结果是增加一个int的大小
  • 如果是一个char *,+1的结果是增加一个char大小
#include <stdio.h>

int main()
{
	int a = 10;
	int *p = &a;
	p += 2;//移动了2个int

	//通过改变指针指向操作数组元素
	int arr[] = {1,2,3,4,5,6,7,8,9};
	int p2 = arr;
	*p2 = 10; //p2指向的是arr的首地址arr[0],arr[0] = 10

	p2++;  //现在p2+1了,指向的是arr[1]


	//指针的步长
	//char类型占一个字节,所以跳跃1字节数
    char* p = NULL;
    printf("%d\n",p);  // 0
    printf("%d\n",p+1);  //1
    
    //double类型占8个字节,所以跳跃8字节数
    double* pp = NULL;
    printf("%d\n",pp);  // 0
    printf("%d\n",pp+1);  //1

	return 0;
}

06. 二级指针和多级指针

二级指针:存放的是1级指针的地址
三级指针:存放的是2级指针的地址

C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。三级指针以上基本用不着。

	int a = 10;
	int *p = &a; //一级指针
	*p = 100; //*p就是a

	int **pp = &p;
	//*pp就是p
	//**pp就是a

	int ***ppp = &pp;
	//*ppp就是pp
	//**ppp就是p
	//***ppp就是a

07. 指针和函数

函数形参改变实参的值

#include <stdio.h>

void swap1(int x, int y)
{
	int tmp;
	tmp = x;
	x = y;
	y = tmp;
	printf("x = %d, y = %d\n", x, y);
}

void swap2(int *x, int *y)
{
	int tmp;
	tmp = *x;
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 3;
	int b = 5;
	swap1(a, b); //值传递
	printf("a = %d, b = %d\n", a, b);

	a = 3;
	b = 5;
	swap2(&a, &b); //地址传递
	printf("a2 = %d, b2 = %d\n", a, b);

	return 0;
}

数组名做函数参数,函数的形参会退化为指针

#include <stdio.h>

//void printArrary(int a[10], int n)
//void printArrary(int a[], int n)
void printArrary(int *a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", a[i]);
	}
	printf("\n");
}

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int n = sizeof(a) / sizeof(a[0]);

	//数组名做函数参数
	printArrary(a, n); 
	return 0;
}

指针做为函数的返回值

#include <stdio.h>

int a = 10;

int *getA()
{
	return &a;
}

int main()
{
	*( getA() ) = 111;
	printf("a = %d\n", a);

	return 0;
}

指针做函数参数

指针做函数参数,具备输入和输出特性:

  • 输入特性:在主调函数中分配内存,被调函数使用
  • 输出特性:在被调函数中分配内存,主调函数使用

输入特性

#include <stdio.h>

void func01(char* p)
{
    //被调函数中使用
    strcpy(p,"hello");
}

int main()
{
    //主调函数在栈上分配了内存,
    char buf[1024] = {0};
    func(buf); //输入特性
    
    return 0;
}

输出特性

#include <stdio.h>

void func01(char** p)
{
    //在被调函数 堆区中分配内存
    char* str = (char*)malloc(sizeof(char)*64);
	memset(str,0,64); //置0
    strcpy(str,"hello");
    *p = str;
}

int main()
{
    char* p = NULL;
    func01(&p);
    
    //主调函数使用
    printf("%s\n",p);
    
    return 0;
}

08. 指针和字符串

字符指针

#include <stdio.h>

int main()
{
	char str[] = "hello world";
	char *p = str;
	*p = 'm';
	p++;
	*p = 'i';
	printf("%s\n", str);

	p = "mike jiang";
	printf("%s\n", p);

	char *q = "test";
	printf("%s\n", q);

	return 0;
}

字符指针做函数参数

#include <stdio.h>

void mystrcat(char *dest, const char *src)
{
	int len1 = 0;
	int len2 = 0;
	while (dest[len1])
	{
		len1++;
	}
	while (src[len2])
	{
		len2++;
	}

	int i;
	for (i = 0; i < len2; i++)
	{
		dest[len1 + i] = src[i];
	}
}

int main()
{
	char dst[100] = "hello mike";
	char src[] = "123456";
	
	mystrcat(dst, src);
	printf("dst = %s\n", dst);

	return 0;
}

指针数组做为main函数的形参

  • main函数是操作系统调用的,第一个参数标明argv数组的成员数量,argv数组的每个成员都是char *类型

  • argv是命令行参数的字符串数组

  • argc代表命令行参数的数量,程序名字本身算一个参数

#include <stdio.h>

//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, char *argv[])
{

	//指针数组,它是数组,每个元素都是指针
	char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
	int i = 0;

	printf("argc = %d\n", argc);
	for (i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]);
	}
	return 0;
}

09. 字符串指针强化

//字符串基本操作
//字符串是以0或者'\0'结尾的字符数组,(数字0和字符'\0'等价)
void test01(){

	//字符数组只能初始化5个字符,当输出的时候,从开始位置直到找到0结束
	char str1[] = { 'h', 'e', 'l', 'l', 'o' };
	printf("%s\n",str1);

	//字符数组部分初始化,剩余填0
	char str2[100] = { 'h', 'e', 'l', 'l', 'o' };
	printf("%s\n", str2);

	//如果以字符串初始化,那么编译器默认会在字符串尾部添加'\0'
	char str3[] = "hello";
	printf("%s\n",str3);
	printf("sizeof str:%d\n",sizeof(str3));
	printf("strlen str:%d\n",strlen(str3));

	//sizeof计算数组大小,数组包含'\0'字符
	//strlen计算字符串的长度,到'\0'结束

	//那么如果我这么写,结果是多少呢?
	char str4[100] = "hello";
	printf("sizeof str:%d\n", sizeof(str4));
	printf("strlen str:%d\n", strlen(str4));

	//请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
	char str5[] = "hello\0world"; 
	printf("%s\n",str5);
	printf("sizeof str5:%d\n",sizeof(str5));
	printf("strlen str5:%d\n",strlen(str5));

	//再请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
	char str6[] = "hello\012world";
	printf("%s\n", str6);
	printf("sizeof str6:%d\n", sizeof(str6));
	printf("strlen str6:%d\n", strlen(str6));
}

字符串拷贝实现

//拷贝方法1
void copy_string01(char* dest, char* source ){

	for (int i = 0; source[i] != '\0';i++){
		dest[i] = source[i];
	}

}

//拷贝方法2
void copy_string02(char* dest, char* source){
	while (*source != '\0' /* *source != 0 */){
		*dest = *source;
		source++;
		dest++;
	}
}

//拷贝方法3
void copy_string03(char* dest, char* source){
	//判断*dest是否为0,0则退出循环
	while (*dest++ = *source++){}
}

字符串反转实现

void reverse_string(char* str){

	if (str == NULL){
		return;
	}

	int begin = 0;
	int end = strlen(str) - 1;
	
	while (begin < end){
		
		//交换两个字符元素
		char temp = str[begin];
		str[begin] = str[end];
		str[end] = temp;

		begin++;
		end--;
	}
}

void test(){
	char str[] = "abcdefghijklmn";
	printf("str:%s\n", str);
	reverse_string(str);
	printf("str:%s\n", str);
}

字符串的格式化

sprintf()

void test(){
	
	//1. 格式化字符串
	char buf[1024] = { 0 };
	sprintf(buf, "你好,%s,欢迎加入我们!", "John");
	printf("buf:%s\n",buf);

	memset(buf, 0, 1024);
	sprintf(buf, "我今年%d岁了!", 20);
	printf("buf:%s\n", buf);

	//2. 拼接字符串
	memset(buf, 0, 1024);
	char str1[] = "hello";
	char str2[] = "world";
	int len = sprintf(buf,"%s %s",str1,str2);
	printf("buf:%s len:%d\n", buf,len);

	//3. 数字转字符串
	memset(buf, 0, 1024);
	int num = 100;
	sprintf(buf, "%d", num);
	printf("buf:%s\n", buf);
	//设置宽度 右对齐
	memset(buf, 0, 1024);
	sprintf(buf, "%8d", num);
	printf("buf:%s\n", buf);
	//设置宽度 左对齐
	memset(buf, 0, 1024);
	sprintf(buf, "%-8d", num);
	printf("buf:%s\n", buf);
	//转成16进制字符串 小写
	memset(buf, 0, 1024);
	sprintf(buf, "0x%x", num);
	printf("buf:%s\n", buf);

	//转成8进制字符串
	memset(buf, 0, 1024);
	sprintf(buf, "0%o", num);
	printf("buf:%s\n", buf);
}

sscanf()函数

格式作用
%*s或%*d跳过数据
%[width]s读指定宽度的数据
%[a-z]匹配a到z中任意字符(尽可能多的匹配)
%[aBc]匹配a、B、c中一员,贪婪性
%[ ^a ]匹配非a的任意字符,贪婪性
%[ ^a-z ]表示读取除a-z以外的所有字符
//1. 跳过数据
void test01(){
	char buf[1024] = { 0 };
	//跳过前面的数字
	//匹配第一个字符是否是数字,如果是,则跳过
	//如果不是则停止匹配
	sscanf("123456aaaa", "%*d%s", buf); 
	printf("buf:%s\n",buf);
}

//2. 读取指定宽度数据
void test02(){
	char buf[1024] = { 0 };
	//跳过前面的数字
	sscanf("123456aaaa", "%7s", buf);
	printf("buf:%s\n", buf);
}

//3. 匹配a-z中任意字符
void test03(){
	char buf[1024] = { 0 };
	//跳过前面的数字
	//先匹配第一个字符,判断字符是否是a-z中的字符,如果是匹配
	//如果不是停止匹配
	sscanf("abcdefg123456", "%[a-z]", buf);
	printf("buf:%s\n", buf);
}

//4. 匹配aBc中的任何一个
void test04(){
	char buf[1024] = { 0 };
	//跳过前面的数字
	//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
	sscanf("abcdefg123456", "%[aBc]", buf);
	printf("buf:%s\n", buf);
}

//5. 匹配非a的任意字符
void test05(){
	char buf[1024] = { 0 };
	//跳过前面的数字
	//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
	sscanf("bcdefag123456", "%[^a]", buf);
	printf("buf:%s\n", buf);
}

//6. 匹配非a-z中的任意字符
void test06(){
	char buf[1024] = { 0 };
	//跳过前面的数字
	//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
	sscanf("123456ABCDbcdefag", "%[^a-z]", buf);
	printf("buf:%s\n", buf);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值