02-指针强化

空指针和野指针

  1. 空指针 —不允许向NULL和非法地址拷贝内存
  2. 野指针
  • 未初始化指针
  • malloc后也free了,但是指针没有置空
  • 指针操作超越变量作用域
  1. 空指针可以释放 ,但是野指针不可以释放

野指针的几种情况

int *dowork()
{
    int a = 10;
    int *p = &a;
    return p;
}

// 野指针情况
void test01()
{
    // 1.声明未初始化指针
    // int *p;
    // printf("%d\n", *p);
3
    // 2. malloc后free的指针
    int *p = (int *)malloc(sizeof(int));
    *p = 10;
    printf("%d\n", *p);

    free(p);

    printf("%d\n", *p);
    // *p=100; 不要操作野指针
    p = NULL;

    // 3.指针变量超出了作用域
    int *p2 = dowork();
    // 第一次打印正常是因为编译器有保护机制
    // 但是因为函数返回的变量存储在栈区,已经被释放了,操作没有意义
    printf("p2 = %d\n", *p2);
    printf("p2 = %d\n", *p2);


    // 空指针可以重复释放,free底层会判断是否是空指针,是的话直接就返回了
    // 但是野指针不可以重复释放,因为第一次释放完,已经无法再操作那个地址了

}

指针的步长

// 指针的步长
// 1.指针变量+1后跳跃的字节数
void test01()
{
    char *p = NULL;

    printf("%d\n", p);
    printf("%d\n", p+1);

    double *p2 = NULL;
    printf("%d\n",p2);
    printf("%d\n",p2+1);
}

// 2.在解引用的时候,取出的字节数
void test02()
{
    char buf[1024] = {0};
    int a = 1000;

    memcpy(buf, &a, sizeof(a));
    char *p = buf;
    printf("%d\n", *(int*)p);

    memcpy(buf+1, &a, sizeof(a));
    char *p2 = buf;
    printf("%d\n", *(int*)(p2+1));
}

指针步长练习

#include <stddef.h>

// 指针步长训练
typedef struct Person
{
    char a; // 0-3
    int b;  // 4-7
    char buf[64];   // 8-71
    int d;  // 72-75
}my_person;
void test03()
{
    my_person p = {'a', 10, "hello world", 10000};

    // p中的d属性偏移量是多少?
    // offset宏函数,可以获取结构体中属性的偏移量
    printf("d的偏移量为: %d\n", offsetof(my_person, d));
    int d_offset = offsetof(my_person, d);

    char *p2 = (char*)&p;
    printf("d的值为: %d\n", *(int *)(p2+d_offset));
}

指针间接赋值

// 间接赋值的三大条件
// 1.两个变量 (普通变量、指针变量 或者 实参+形参)
// 2.建立关系
// 3.通过*操作内存
void test01()
{
    int a = 10;
    int *p = NULL;

    p = &a;
    *p = 20;
    printf("%d\n", a);
}

// 
void valChanged(int *a)
{
    *a = 100;
}
void test02()
{
    int a = 10;
    valChanged(&a);
    printf("%d\n", a);
    
}

指针做函数参数的输入输出特性

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

字符串注意事项

//字符串基本操作
//字符串是以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);   // \012 八进制数字,转为十进制 10 对应ASCII换行
	printf("sizeof str6:%d\n", sizeof(str6));
	printf("strlen str6:%d\n", strlen(str6));
}

字符串拷贝

// 第一种拷贝方式
void copyString01(char *dest, char *src)
{
    // 利用下标方式拷贝
    int i = 0;
    for (i = 0; src[i]!= '\0'; i++)
    {
        dest[i] = src[i];
    }
    dest[i] = '\0';
}

// 第二种拷贝方式
void copyString02(char *dest, char *src)
{
    // 利用字符串指针
    while (*src != '\0')
    {   
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';
    
}
// 第三种拷贝方式,这个有些难度
void copyString03(char *dest, char *src)
{
    while (*dest++ = *src++);
    {
        /* code */
    }
    
}
void test02(){
    char *str = (char *)"hello world";
    char buf[1024];

    // copyString01(buf, str);
    // copyString02(buf, str);
    copyString03(buf, str);
    printf("buf = %s\n", buf);
}

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);
}

calloc和relloc

// calloc
void test01(){

    // malloc不会做清0的操作
    // int *p = malloc(10*sizeof(int));
    // 参数1 开辟个数  参数2  每个占多少字节数
    // 相同的是都是在堆区开辟空间
    // 不同的是,开辟空间后置0的操作
    int *p = (int*)calloc(10, sizeof(int));
    for (int i = 0; i < 10; i++) {
        printf("%d\n", p[i]);
    }
    free(p);
    p = NULL;
}

// relloc
void test02()
{
    // 功能,重新分配内存
    int *p = (int*)malloc(10*sizeof(int));

    for (int i = 0; i < 10; i++) {
        p[i] = i;
    }
    for (int i = 0; i < 10; i++) {
        printf("%d\n", p[i]);
    }

    printf("p的地址:%d\n");
    // 参数1 原空间的首地址, 参数2 重新分类内存大小
    p = (int*)realloc(p, 20*sizeof(int));


    printf("p的地址:%d\n");
    // 新开辟的空间,不会清空里面的内容
    for (int i = 0; i < 20; i++) {
        printf("%d\n", p[i]);
    }

}

sscanf()


#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
功能:
    从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
	str:指定的字符串首地址
	format:字符串格式,用法和scanf()一样
返回值:
	成功:成功则返回参数数目,失败则返回-1
	失败: - 1


格式	作用
/*
%*s或%*d	跳过数据
%[width]s	读指定宽度的数据
%[a-z]	匹配a到z中任意字符(尽可能多的匹配)
%[aBc]	匹配a、B、c中一员,贪婪性
%[^a] 	匹配非a的任意字符,贪婪性
%[^a-z]	表示读取除a-z以外的所有字符
*/

```void test01()
{
    char *str = (char*)"abcde12345";

    char buffer[1024] = { 0 };

    // %*d 忽略
    sscanf(str, "%*[a-z]%s", buffer);
    printf("%s\n", buffer);
}

// 2. %[width]s 读取指定宽度的数据
void test03()
{
    char *str = (char*)"abcde12345";

    char buffer[1024] = { 0 }; 
    sscanf(str, "%6s", buffer);
    printf("%s\n", buffer);


}

void test04()
{
    char *str = (char*)"12345abcdeA";

    char buffer[1024] = { 0 };

    // %*d 忽略
    sscanf(str, "%*d%[a-z&A-Z]%s", buffer);
    printf("%s\n", buffer);
}

// 4. %[aBc] 匹配a,B,c中的一员,贪婪性
void test05()
{
    // 如果遇到匹配失败,后续不再进行匹配
    char *str = (char*)"aabcdabcf12341";
    char buffer[1024] = { 0 };

    sscanf(str, "%[abc]", buffer);
    printf("%s\n", buffer);
}

// 5. %[^a] 匹配非a的任意字符,贪婪性
void test06()
{
    char *str = (char*)"baabcdabcf12341";
    char buffer[1024] = { 0 };

    sscanf(str, "%[^a]", buffer);
    printf("%s\n", buffer);

}
// 5. %[^a-z] 匹配非a的任意字符,贪婪性
// sscanf练习
void test07()
{
    char *str = (char*)"abcd#zhangtao@12345adf";
    char buffer[1024] = { 0 };
    // %*[^#]#  %*先忽略掉#和#之前的字符   然后再获取@之前的姓名
    sscanf(str, "%*[^#]#%[^@]", buffer);
    printf("buffer = %s\n", buffer);
}

void test08()
{
    char *str = (char*)"helloworld@itcast.cn";
    char buffer1[1024] = { 0 };
    char buffer2[1024] = { 0 };
    sscanf(str, "%[a-z]%*[@]%s", buffer1, buffer2);
    printf("buffer1 = %s, buffer2 = %s\n", buffer1, buffer2);
}

void test09()
{
	char *ip = (char*)"127.0.0.1";
	int num1, num2, num3, num4;
	sscanf(ip, "%d.%d.%d.%d.", &num1, &num2, &num3, &num4);
	printf("num1 = %d\nnum2 = %d\nnum3 = %d\nnum4 = %d\n",
			num1, num2, num3, num4);
}

一级指针易错点

越界

    // array must be initialized with a brace-enclosed initializer
    char buf[3] = (char*)"abc";
    printf("%s\n", buf);

这里试图将一个字符串常量 “abc” 强制转换为 char* 类型,并将其赋值给一个长度为 3 的字符数组 buf。然而,这个操作是不合法的,因为字符串常量是不可修改的,尝试将其赋值给一个字符数组会导致未定义的行为。

要修复这个问题,你可以将 buf 声明为字符数组,并初始化为字符串常量的内容,但是你需要确保数组大小足够容纳字符串以及一个 null 结尾字符 ‘\0’,因为 C 字符串必须以 null 结尾。

可以不指定buf的大小,编译器会根据初始化字符串的长度自动分配足够的空间,而且字符串也会以‘\0’结尾。

指针叠加会不断改变指针指向

void test01()
{
    char *p = (char*)malloc(10*sizeof(char));
    char *pp = p;
    for(int i = 0; i < 10; i++)
    {
        *pp = i + 97;
        printf("%c ", *pp);
        pp++;
    }

	// 如果这里不用pp修改指针指向,直接操作p,在释放时会出错
    if (p) {
        free(p);
        p = NULL;
    }
}

返回局部变量的地址

char *get_str()
{
	// 这个字符串是在定义在栈区上的,函数执行完,空间就被释放了,再操作为非法
	char str[] = "abcdedsgads"; //栈区,
	printf("[get_str]str = %s\n", str);
	return str;
}

同一块内存释放次(不能释放野指针)

void test(){	
	char *p = NULL;
	p = (char *)malloc(50);
	strcpy(p, "abcdef");

	if (p != NULL)
	{
		//free()函数的功能只是告诉系统 p 指向的内存可以回收了
		// 就是说,p 指向的内存使用权交还给系统
		//但是,p的值还是原来的值(野指针),p还是指向原来的内存
		free(p); 
	}

	if (p != NULL)
	{
		free(p);
	}
}

const使用场景

// const使用场景
typedef struct Person
{
    char *name;
    int age;
    char id[64];
    double score;
} Person;

// 使用const修饰形参,防止对数据进行修改
void printPerson(const Person *p)
{
    printf("姓名: %s 年龄:%d id: %s, 得分 %f\n", p->name, p->age, p->id, p->score);
}
void test02()
{
    Person p = {(char*)"TOM", 18, "120110", 78};
    printPerson(&p);
}

二级指针做形参输入特性

//打印数组
void print_array(int **arr,int n){
	for (int i = 0; i < n;i ++){
		printf("%d ",*(arr[i]));
	}
	printf("\n");
}
//二级指针输入特性(由主调函数分配内存)
void test(){

	// 在堆区开辟内存,管理栈区
	int a1 = 10;
	int a2 = 20;
	int a3 = 30;
	int a4 = 40;
	int a5 = 50;

	int n = 5;

	int** arr = (int **)malloc(sizeof(int *) * n);
	arr[0] = &a1;
	arr[1] = &a2;
	arr[2] = &a3;
	arr[3] = &a4;
	arr[4] = &a5;

	print_array(arr,n);

	free(arr);
	arr = NULL;
}
// 在栈上开辟内存,管理堆区
void test02()
{
	int *parray[5];
	for (int i = 0; i < 5; i++)
	{
		parray[i] = (int*)malloc(4);
		*(parray[i]) = i+100;
	}
	int len = sizeof(parray) / sizeof(int*);
	// 数组名作形参的时候,会退化成指针
	print_array(parray, len);

	for (int i  = 0; i < len; i++)
	{
		if (parray[i]){
			free(parray[i]);
			parray[i] = NULL;
		}
	}
}

二级指针做形参输出特性

// 二级指针做函数参数的输出特性, 在被调函数中开辟空间
void allocateSpace(int **pp)
{
    
    int *parr = (int*)malloc(sizeof(int) * 10);
    for (int i = 0; i < 10; i++)
    {
        parr[i] = i + 100;
    }
    *pp = parr;
}

// 想要操作指针所指向的空间,形参用同级指针
// 想要操作指针本身,形参用更高一级的指针
void printarray(int **arr, int len)
{
    for (int i = 0; i < len; i++)
    {
        printf("%d ", (*arr)[i]);
    }

}
void freearray(int **arr)
{
    if (*arr)
    {
        free(*arr);
        *arr = NULL;
    }
}
void test03()
{
    int *parr = NULL;
    allocateSpace(&parr);
    printarray(&parr, 10);
    // 释放堆区的数据
    freearray(&parr);
}

二级指针练习

文件读写1

这里需要注意,获取文件行数之后,需要将文件光标置首,不然会影响后面的读

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 获取文件的有效行数
int getFileLines(FILE *file)
{

    // 这种方法内存开销小,但是效率低
    // char c;
    // while ((c = fgetc(file))!= EOF) {
    //     if (c == '\n') {
    //         count++;
    //     }
    // }

    // 这种方法更好理解,性能较好
    int count = 0;
    char buffer[1024];
    while ((fgets(buffer, 1024, file))!= NULL) {
        // printf("%s", buffer);
        count++;
    }

    // ***这里需要再将光标置首,不然会影响后面继续读的操作
    fseek(file, 0, SEEK_SET);
    return count;
}
void readFileData(FILE * fileno, char ** parray, int length)
{
    int i = 0;
    char buffer[1024];
    while (i < length) 
    {       
        fgets(buffer, 1024, fileno);

        // 删除换行符

        // 1.strtok是用来分割字符串的
        // strtok(buffer, "\n");

        // 2.将每行的最后自带的换行换为\0
        buffer[length - 1] = '\0';
        // 先申请空间
        parray[i] = (char *)malloc(strlen(buffer) + 1);
        strcpy(parray[i], buffer);
        // printf("%s ", buffer);
        memset(buffer, 0, 1024);
        i++;
    }
    
}

void showFileData(FILE * fileno, char ** parray, int length)
{
    for (int i = 0; i < length; i++)
    {
        printf("第%d行的数据是:%s\n", i+1, parray[i]);
    }
}
// 释放堆区空间
void freeSpace(char ** pArray, int length)
{
    // 二级指针指向的地址还有开辟的空间
    // 所以要先释放小的地址,最后再释放二级指针
    for (int i = 0; i < length; i++)
    {
        if (pArray[i] != NULL) {
            printf("%s被释放了\n", pArray[i]);
            free(pArray[i]);
            pArray[i] = NULL;
        }
    }
    free(pArray);
    pArray = NULL;
}
void test01()
{
    FILE *file = fopen("test.txt", "r");
    if (file == NULL) 
    {
        printf("文件打开失败\n");
        return;
    }
        
    int count = getFileLines(file);
    printf("文件有效行数count = %d\n", count);


    char **parray = (char**)malloc(sizeof(char*)*count);
    readFileData(file, parray, count);
    showFileData(file, parray, count);

    freeSpace(parray, count);
    // 关闭文件
    fclose(file);
    file = NULL;
}
int main(int argc, char const *argv[])
{
    // test();
    // test02();
    test01();
    return 0;
}

按位运算、左移和右移

// 取反操作
void test01()
{
    int num = 2;
    printf("num = %d\n", ~num);
    // 2:  0  10
    //    1  01 源码
    //    1  10+1=11  
    //    111 补码=-3
}
// 按位与
void test02()
{
    int num = 112;
    if ((num & 1) == 0)
        printf("偶数 \n");
    else
        printf("奇数 \n");
}
// 按位或
void test03()
{
    int num1 = 5, num2 = 3;
    printf("num1 | num2 = %d \n", num1 | num2);
    // 0101 | 0011 = 0111
    // 可以将指定位置置位1
}    

// 按位异或
void test04()
{
    int num1 = 5, num2 = 9;
    
    // 实现两个数交换, 方法1
    // int temp = num1;
    // num1 = num2;
    // num2 = temp;

    // 方法2
    // num1 = num1 ^ num2;
    // num2 = num1 ^ num2;
    // num1 = num1 ^ num2;

    // 方法3 这种方法不需要开辟新的空间
    num1 = num1 + num2;
    num2 = num1 - num2;
    num1 = num1 - num2;
    printf("num1 = %d num2 = %d \n", num1, num2);

}

// 左移和右移 
void test05()
{
    int num = 25;
    // 左移n位代表乘以2^n
    printf("num << 3 = %d \n", num <<=3);
    // 左移n位代表除以2^n
    printf("num >> 1 = %d \n", num >>=1);
   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值