指针是一种数据类型

指针变量

指针是一种数据类型,占用内存空间,用来保存内存地址。

void test01(){    int* p1 = 0x1234;  int*** p2 = 0x1111;  printf("p1 size:%d\n",sizeof(p1));  printf("p2 size:%d\n",sizeof(p2));  //指针是变量,指针本身也占内存空间,指针也可以被赋值  int a = 10;  p1 = &a;  printf("p1 address:%p\n", &p1);  printf("p1 address:%p\n", p1);  printf("a address:%p\n", &a);}

野指针和空指针

空指针

标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。

对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未执行任何东西,因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NULL指针。

如果对一个NULL指针间接访问会发生什么呢?结果因编译器而异。

不允许向NULL和非法地址拷贝内存:

void test(){  char *p = NULL;  //给p指向的内存区域拷贝内容  strcpy(p, "1111"); //err  char *q = 0x1122;  //给q指向的内存区域拷贝内容  strcpy(q, "2222"); //err    }

野指针

在使用指针时,要避免野指针的出现:

野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

什么情况下会导致野指针?

  • 指针变量未初始化

    任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

  • 指针释放后未置空

    有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

  • 指针操作超越变量作用域

    不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

void test(){  int* p = 0x001; //未初始化  printf("%p\n",p);  *p = 100;}

操作野指针是非常危险的操作,应该规避野指针的出现:

  • 初始化时置 NULL

    指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。

  • 释放时置 NULL

    当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。

间接访问操作符

通过一个指针访问它所指向的地址的过程叫做间接访问,或者叫解引用指针,这个用于执行间接访问的操作符是*。

注意:对一个int类型指针解引用会产生一个整型值,类似地,对一个float指针解引用会产生了一个float类型的值。

int arr[5];  int *p = * (&arr);int arr1[5][3] arr1 = int(*)[3]&arr1
  • 在指针声明时,* 号表示所声明的变量为指针

  • 在指针使用时,* 号表示操作指针所指向的内存空间
    1)* 相当通过地址(指针变量的值)找到指针指向的内存,再操作内存
    2)* 放在等号的左边赋值(给内存赋值,写内存)
    3)* 放在等号的右边取值(从内存中取值,读内存)

//解引用void test01(){  //定义指针  int* p = NULL;  //指针指向谁,就把谁的地址赋给指针  int a = 10;  p = &a;  *p = 20;//*在左边当左值,必须确保内存可写  //*号放右面,从内存中读值  int b = *p;  //必须确保内存可写  char* str = "hello world!";  *str = 'm';  printf("a:%d\n", a);  printf("*p:%d\n", *p);  printf("b:%d\n", b);}

指针的步长

指针是一种数据类型,是指它指向的内存空间的数据类型。指针所指向的内存空间决定了指针的步长。指针的步长指的是,当指针+1时候,移动多少字节单位。

思考如下问题:

int a = 0xaabbccdd;unsigned int *p1 = &a;unsigned char *p2 = &a;//为什么*p1打印出来正确结果?printf("%x\n", *p1);//为什么*p2没有打印出来正确结果?printf("%x\n", *p2);//为什么p1指针+1加了4字节?printf("p1  =%d\n", p1);printf("p1+1=%d\n", p1 + 1);//为什么p2指针+1加了1字节?printf("p2  =%d\n", p2);printf("p2+1=%d\n", p2 + 1);

指针的意义_间接赋值

间接赋值的三大条件

通过指针间接赋值成立的三大条件:
1)2个变量(一个普通变量一个指针变量、或者一个实参一个形参)
2)建立关系
3)通过 * 操作指针指向的内存

void test(){  int a = 100;  //两个变量  int *p = NULL;  //建立关系  //指针指向谁,就把谁的地址赋值给指针  p = &a;  //通过*操作内存  *p = 22;}

如何定义合适的指针变量

void test(){  int b;    int *q = &b; //0级指针  int **t = &q;  int ***m = &t;}

间接赋值:从0级指针到1级指针

int func1(){ return 10; }void func2(int a){  a = 100;}//指针的意义_间接赋值void test02(){  int a = 0;  a = func1();  printf("a = %d\n", a);  //为什么没有修改?  func2(a);  printf("a = %d\n", a);}//指针的间接赋值void func3(int* a){  *a = 100;}void test03(){  int a = 0;  a = func1();  printf("a = %d\n", a);  //修改  func3(&a);  printf("a = %d\n", a);}

间接赋值:从1级指针到2级指针

void AllocateSpace(char** p){  *p = (char*)malloc(100);  strcpy(*p, "hello world!");}void FreeSpace(char** p){  if (p == NULL){    return;  }  if (*p != NULL){    free(*p);    *p = NULL;  }}void test(){    char* p = NULL;  AllocateSpace(&p);  printf("%s\n",p);  FreeSpace(&p);  if (p == NULL){    printf("p内存释放!\n");  }}

间接赋值的推论

  • 用1级指针形参,去间接修改了0级指针(实参)的值。

  • 用2级指针形参,去间接修改了1级指针(实参)的值。

  • 用3级指针形参,去间接修改了2级指针(实参)的值。

  • 用n级指针形参,去间接修改了n-1级指针(实参)的值。

指针做函数参数

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

  • 输入:主调函数分配内存

  • 输出:被调用函数分配内存

输入特性

void fun(char *p /* in */){  //给p指向的内存区域拷贝内容  strcpy(p, "abcddsgsd");}void test(void){  //输入,主调函数分配内存  char buf[100] = { 0 };  fun(buf);  printf("buf  = %s\n", buf);}

输出特性

void fun(char **p /* out */, int *len){  char *tmp = (char *)malloc(100);  if (tmp == NULL)  {    return;  }  strcpy(tmp, "adlsgjldsk");  //间接赋值  *p = tmp;  *len = strlen(tmp);}void test(void){  //输出,被调用函数分配内存,地址传递  char *p = NULL;  int len = 0;  fun(&p, &len);  if (p != NULL)  {    printf("p = %s, len = %d\n", p, len);  }

字符串指针强化

字符串指针做函数参数

字符串基本操作

//字符串基本操作//字符串是以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));}

八进制和十六进制转义字符:

在C中有两种特殊的字符,八进制转义字符和十六进制转义字符,八进制字符的一般形式是’\ddd’,d是0-7的数字。十六进制字符的一般形式是’\xhh’,h是0-9或A-F内的一个。八进制字符和十六进制字符表示的是字符的ASCII码对应的数值。

比如 :

  • ‘\063’表示的是字符’3’,因为’3’的ASCII码是30(十六进制),48(十进制),63(八进制)。

  • ‘\x41’表示的是字符’A’,因为’A’的ASCII码是41(十六进制),65(十进制),101(八进制)。

字符串拷贝功能实现

//拷贝方法1void copy_string01(char* dest, char* source ){  for (int i = 0; source[i] != '\0';i++){    dest[i] = source[i];  }}//拷贝方法2void copy_string02(char* dest, char* source){  while (*source != '\0' /* *source != 0 */){    *dest = *source;    source++;    dest++;  }}//拷贝方法3void copy_string03(char* dest, char* source){  //判断*dest是否为0,0则退出循环  while (*dest++ = *source++){}}//拷贝方法4//1)应该判断下传入的参数是否为NULL//2)最好不要直接使用形参int copy_string04(char* dest, char* source){  if (dest == NULL){    return -1;  }  if (source == NULL){    return -2;  }  char* src = source;  char* tar = dest;  while (*tar++ = *src++){}  return 0;}

字符串反转模型

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

#include int sprintf(char *str, const char *format, ...);功能:     根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到    出现字符串结束符 '\0'  为止。参数:  str:字符串首地址  format:字符串格式,用法和printf()一样返回值:  成功:实际格式化的字符个数  失败: - 1
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

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

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

一级指针易错点

越界

void test(){  char buf[3] = "abc";  printf("buf:%s\n",buf);}

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

void test(){  char *p = (char *)malloc(50);  char buf[] = "abcdef";  int n = strlen(buf);  int i = 0;  for (i = 0; i < n; i++)  {    *p = buf[i];    p++; //修改原指针指向  }  free(p);}

返回局部变量地址

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修饰变量void test01(){  //1. const基本概念  const int i = 0;  //i = 100; //错误,只读变量初始化之后不能修改  //2. 定义const变量最好初始化  const int j;  //j = 100; //错误,不能再次赋值  //3. c语言的const是一个只读变量,并不是一个常量,可通过指针间接修改  const int k = 10;  //k = 100; //错误,不可直接修改,我们可通过指针间接修改  printf("k:%d\n", k);  int* p = &k;  *p = 100;  printf("k:%d\n", k);}//const 修饰指针void test02(){  int a = 10;  int b = 20;  //const放在*号左侧 修饰p_a指针指向的内存空间不能修改,但可修改指针的指向  const int* p_a = &a;  //*p_a = 100; //不可修改指针指向的内存空间  p_a = &b; //可修改指针的指向  //const放在*号的右侧, 修饰指针的指向不能修改,但是可修改指针指向的内存空间  int* const p_b = &a;  //p_b = &b; //不可修改指针的指向  *p_b = 100; //可修改指针指向的内存空间  //指针的指向和指针指向的内存空间都不能修改  const int* const p_c = &a;}//const指针用法struct Person{  char name[64];  int id;  int age;  int score;};//每次都对对象进行拷贝,效率低,应该用指针void printPersonByValue(struct Person person){  printf("Name:%s\n", person.name);  printf("Name:%d\n", person.id);  printf("Name:%d\n", person.age);  printf("Name:%d\n", person.score);}//但是用指针会有副作用,可能会不小心修改原数据void printPersonByPointer(const struct Person *person){  printf("Name:%s\n", person->name);  printf("Name:%d\n", person->id);  printf("Name:%d\n", person->age);  printf("Name:%d\n", person->score);}void test03(){  struct Person p = { "Obama", 1101, 23, 87 };  //printPersonByValue(p);  printPersonByPointer(&p);}

原文:https://blog.csdn.net/tichimi3375/article/details/104722295

END

如果您觉得本篇文章对您有帮助请转发给更多的人

顺手点一下“在看”也是对小编最大的支持

c6011取消对null指针的引用_C/C++学习笔记——C提高:指针强化相关推荐

  1. c++ 指针_C/C++学习笔记——C提高:指针强化

    指针是一种数据类型 指针变量 指针是一种数据类型,占用内存空间,用来保存内存地址. void test01(){ int* p1 = 0x1234; int*** p2 = 0x1111; print ...

  2. c6011取消对null指针的引用_C++中的野指针及其规避方法

    今天在调试程序过程中,用到了一些指针的方法,这里记录一下野指针的概念. 1.概念 野指针,也就是指向不可用内存区域的指针.通常对这种指针进行操作的话,将会使程序发生不可预知的错误. 野指针与空指针(N ...

  3. c6011取消对null指针的引用_C++中的引用

    当变量声明为引用时,它将成为现有变量的替代名称.通过在声明中添加"&",可以将变量声明为引用. #include using namespace std; int main ...

  4. c6011取消对null指针的引用_C++| 函数的指针参数如何传递内存?

    函数的参数是一个一级指针,可以传递内存吗? 如果函数的参数是一个一级指针,不要指望用该指针去申请动态内存. 看下面的实例: #include using namespace std; void Get ...

  5. c6011取消对null指针的引用_COM编程攻略(二十二 IDL中的枚举,指针,数组)

    上一篇: Froser:COM编程攻略(二十一 异步)​zhuanlan.zhihu.com 本篇主要讲idl的一些语法特性. idl的语法和C语言非常类似,但是它扩展了一些特性,这些特性用于兼容其它 ...

  6. 学习笔记11-C语言-指针

    什么是指针: 指针是一种特护的数据类型,使用它可以定义指针变量,指针变量存储的是整型数据,代表内存的编号,通过这个编号可以访问到对应内存. 为什么使用指针 1.函数与函数之间是相互独立的,但是有些时候 ...

  7. (C/C++学习笔记) 十二. 指针

    十二. 指针 ● 基本概念 变量的地址就是指针,存放指针的变量就是指针变量(因而又叫作地址变量 address variable); 这个地址编号本身就是一个无符号的整数,在32位系统下为4字节(8位 ...

  8. 【C语言学习笔记】26. 指针(3)指向指针的指针、传递指针给函数

    前言 指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链.通常,一个指针包含一个变量的地址.当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置. ...

  9. C语言-指针-基础用法、运算、指针与数组、指针数组、多级指针、void指针和const修饰符-学习笔记08

    文章目录 1. 地址和变量 2. 指针 2.1 基本声明 2.2 指针的目标 3. 指针的运算 3.1 指针的赋值运算 3.2 指针的算术运算 3.2.1 指针与常量的加法与减法 3.2.2 两指针相 ...

最新文章

  1. java使用BigDecimal 处理商业精度及高精度详解
  2. 这样就可以很方便的知道明天的天气了
  3. encodeURI、encodeURIComponent、btoa及其应用场景
  4. SignalR的使用
  5. php 版权信息自动变化,php自动更新版权信息显示的方法
  6. MySQL中Slave_IO_Running: Connecting问题
  7. (7)FPGA十大设计思想(第2天)
  8. 项目代码迁移(使用git)
  9. c++builder 运行网站的api_欧美音乐网站Python爬虫项目实战
  10. 我的技术心病(转贴)
  11. python commands执行不连续_[Python] 利用commands模块执行Linux shell命令
  12. 动态IP和静态IP区别,别傻傻分不清楚!
  13. java中return与finally的执行顺序分析(根据字节码分析)
  14. 大学期末考java编程题_大学慕课2020年Java程序设计期末考试大全答案
  15. ps切图与版心页面布局注意问题
  16. 实验五:常见WEB漏洞挖掘与利用
  17. Java与模式学习笔记 —— 桥梁(Bridge)模式
  18. 最大流最小割定理(max flow/min cut theory)
  19. 衡水东方计算机学校是技校吗,河北衡水技校有哪些 衡水哪个技术学校好
  20. PHP大灌篮投篮游戏源码 微信+手机wap源码 带控制_大灌篮游戏源码

热门文章

  1. python槽格式里填啥_用于填充插槽的网络挂钩
  2. 判断 Python 对象是否包含某个属性的方法
  3. django简介及环境搭建
  4. 利用Jqurey写一个输入内容增加并且可以删除,上下移动的标签
  5. 修改mysql文件的存储路径
  6. 【Spring Cloud中文社区】正式启动
  7. 直通BAT必考题系列:深入剖析JVM之G1收集器、及回收流程、与推荐用例
  8. 唐刘之辩:行业知识图谱的schema构建的难点、重点与困惑
  9. winfrom导出DataGridView为Excel方法
  10. springcloud流程图