空指针和野指针
- 空指针 —不允许向NULL和非法地址拷贝内存
- 野指针
- 未初始化指针
- malloc后也free了,但是指针没有置空
- 指针操作超越变量作用域
- 空指针可以释放 ,但是野指针不可以释放
野指针的几种情况
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);