七、C/C++指针

  • 7.1 概述
    • 7.1.1 内存
    • 7.1.2 物理存储器和存储地址空间
    • 7.1.3 内存地址
    • 7.1.4 指针和指针变量
  • 7.2 指针基础知识
    • 7.2.1 指针变量的定义和使用
    • 7.2.2 指针大小
    • 7.2.3 野指针和空指针
    • 7.2.4 万能指针void *
    • 7.2.5 const修饰的指针变量
  • 7.3 指针和数组的加减运算
    • 7.3.1 加法运算
    • 7.3.2 减法运算
    • 7.3.3 指针数组
  • 7.4 多级指针
  • 7.5 指针和函数
    • 7.5.1 函数形参改变实参的值
    • 7.5.2 数组名做函数参数
    • 7.5.3 指针做为函数的返回值
  • 7.6 指针和字符串
    • 7.6.1 字符指针
    • 7.6.2 字符指针做函数参数
    • 7.6.3 指针数组做为main函数的形参
    • 7.6.4 项目开发常用字符串应用模型
      • 7.6.4.1 字符串查找字符串
      • 7.6.4.2 统计小写字母出现次数
      • 7.6.4.3 字符串反转模型(逆置)
      • 7.6.4.4 回文字符串
    • 7.6.5 字符串处理函数
      • 7.6.5.1 strcpy()
      • 7.6.5.2 strncpy()
      • 7.6.5.3 strcat()
      • 7.6.5.4 strncat()
      • 7.6.5.5 strcmp()
      • 7.6.5.6 strncmp()
      • 7.6.5.7 sprintf()
      • 7.6.5.8 sscanf()
      • 7.6.5.9 strchr()
      • 7.6.5.10 strstr()
      • 7.6.5.11 strtok()
      • 7.6.5.12 atoi()

7.1 概述

只有C语言和C++才有指针,开外挂需要指针访问地址修改数据。

7.1.1 内存

①存储器:计算机组成中用来存储程序和数据,辅助CPU进行运算处理的重要部分;
②内存:内部存贮器,暂存程序/数据,—掉电丢失数据,例如SRAM、DRAM、DDR、DDR2、DDR3;
③外存:外部存储器,长时间保存程序/数据,一掉电不丢失数据,例如ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘。

内存是沟通CPU与硬盘的桥梁:
①暂存放CPU中的运算数据;
②暂存与硬盘等外部存储器交换的数据。

7.1.2 物理存储器和存储地址空间

物理存储器:实际存在的具体存储器芯片。
①主板上装插的内存条;
②各种适配卡上的RAM芯片和ROM芯片;
③32位处理器它的寻址能力是0-232-1B,所以装内存条太大的时候,多余的内存空间没有地址,没有地址(十六进制表示)内存就无法使用。因为232/1024/1024/1024=4GB,也就是说32位操作系统只能装4GB的内存条。
存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这层含义。
①编码:对每个物理存储单元(一个字节)分配一个号码;
②寻址:根据分配的号码找到相应的存储单元,完成数据的读写。

7.1.3 内存地址

①将内存抽象成一个很大的一维字符数组,地址从0开始编号(size_t);
②编码就是对内存的每一个字节分配一个32位或64位的编号(与32位(size_t)或64位(unsigned__int64)处理器相关),内存编号称为内存地址。
内存中的每一个数据都会分配相应的地址:
①char:占一个字节分配一个地址;
②int: 占四个字节分配四个地址;
③float、struct、函数、数组等。
定义一个变量0xaabbccdd,如下图所示(首地址),发现数据是倒着存储(windows在电脑数据存储方式是小端模式。

小端模式:就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
大端模式:就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
例如,比如数字0x12 34 56 78在内存中的表示形式为
①大端模式:   低地址 -----------------> 高地址0x12  |  0x34  |  0x56  |  0x78②小端模式:   低地址 ------------------> 高地址0x78  |  0x56  |  0x34  |  0x12
可见,大端模式和字符串的存储模式类似。16bit宽的数0x1234在小端模式以及大端模式下CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址    小端模式存放内容 大端模式存放内容
0x4000      0x34            0x12
0x4001      0x12            0x3432bit宽的数0x12345678在小端模式以及大端模式下CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址    小端模式存放内容    大端模式存放内容
0x4000          0x78        0x12
0x4001          0x56        0x34
0x4002          0x34        0x56
0x4003          0x12        0x78

7.1.4 指针和指针变量

①如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址;
②指针的实质就是内存“地址”。指针就是地址,地址就是指针;
③指针是内存单元的编号,指针变量是存放地址的变量;
④通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

7.2 指针基础知识

7.2.1 指针变量的定义和使用

指针也是一种数据类型,指针变量也是一种变量,定义格式:数据类型* 指针变量

 int a = 10;    //通过变量名a=0直接修改变量的值 int* p1;    //定义指针变量,既然是变量也可以进行赋值等运算p1 = &a;   //取出a的地址赋值给pprintf("address:%p\n", &a);//打印出地址printf("address:%p\n", p1);//打印出地址,跟上面的地址一致*p1 = 0;               //通过地址间接修改变量的值printf("a=%d\n", *p1); //0printf("a=%d\n", a);  //0

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

7.2.2 指针大小

所有指针类型的变量都是存储内存地址,地址都是无符号十六进制数。
①在32位平台,所有的指针(地址)都是32位(4字节);
②在64位平台,所有的指针(地址)都是64位(8字节)。

 printf("sizeof(char*) = %d\n", sizeof(char*));printf("sizeof(int*) = %d\n", sizeof(int*));printf("sizeof(double*)) = %d\n", sizeof(double*));printf("sizeof(int**) = %d\n", sizeof(int**));

7.2.3 野指针和空指针

问:①既然指针都是无符号十六进制整型,直接int p = &a为什么还要一个* ?
②既然sizeof(指针)都是同样大小,可以全部都定义为int*,不要char* 可否?
①int p = &a,p是一个整型变量,而不是地址,只是存放a的地址变量,如果后面运行* p = 100就会报错。所以要int* p = &a。
②如下图所示,定义字符型变量,指向整型指针变量,打印地址时地址相同,但是在修改* p后出现debug error。

先不修改* p,直接打印ch的值跟* p的值,发现ch正常输出,但是* p不行,说明int*找到对应的内存空间时是4个字节大小的,而char是一个字节。所以在定义指针类型的变量时要跟变量的数据类型对应==。

指针变量也是变量,是变量就可以任意赋值,不要越界即可。但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知的(操作系统不允许操作野指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。==

 int a = 100;int *p;p = a;             //把a的值赋值给指针变量p,p为野指针,不会报错,但没有意义p = 0x12345678; //给指针变量p赋值,p为野指针,不会报错,但没有意义//*p = 1000;      //操作野指针指向未知区域,内存出问题,err //操作系统将0-255作为系统占用不允许直接操作和访问


但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针(地址为0的指针),没有任何指针,常用于条件判断。

 int *p = NULL; //*p = 100;            //error,操作空指针一定会报错,因为0-255是系统占用//printf("%d",*p);   //error,访问指针一定会报错,因为0-255是系统占用

NULL是一个值为0常量(触摸上面代码的NULL,也可以右击转到定义得)。

#define NULL    ((void *)0)

7.2.4 万能指针void *

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

 void *p = NULL;int a = 10;p = (void *)&a; //指向变量时,最好转换为void *//使用指针变量指向的内存时,转换为int **( (int *)p ) = 11;printf("a = %d\n", a);

7.2.5 const修饰的指针变量

//#define 常量名 常量//这种方法定义常量是无法修改的
//#define定义的常量存在数据区
//const定义的常量存在栈区,所以const定义的常量是不安全的const int a = 0;//a = 1;         //说明const定义的常量不能用变量直接修改int* p1 = &a;*p1 = 1;          //说明const定义的常量可以用指针间接修改printf("a  =%d\n", a);    //1printf("*p1=%d\n", *p1);  //1int b = 2, c = 3;      //const离谁近就不能改谁//指向常量的指针,修饰*,指针指向内存区域不能修改,指针指向可以变const int* p2 = &b;      //等价于int const *p2 = &b;//*p2 = 3;            //error,表达式不是可修改的左值p2 = &c;         //okprintf("b  =%d\n", b);       //b不变printf("c  =%d\n", c);      //3printf("*p2=%d\n", *p2);      //3int d = 4, e = 5;//指针常量,修饰p3,指针指向不能变,指针指向的内存可以修改int* const p3 = &d;//p3 = &e;           //error*p3 = 5;            //okprintf("d  =%d\n", d);       //d发生改变printf("e  =%d\n", e);        //5printf("*p3=%d\n", *p3);  //5int f = 6, g = 7;const int* const p4 = &f;    //修饰*跟指针变量//p4 = &g;           //error//*p4 = 7;          //errorint** pp = &p4;         //可以用二级指针修改//*pp = &g;         //ok//printf("f  =%d\n", f);     //6//printf("g  =%d\n", g);      //7//printf("*p4=%d\n", *p4);    //7,*pp = p4 = &g//printf("**pp=%d\n", **pp);  //7**pp = 7;           //okprintf("f  =%d\n", f);       //7printf("g  =%d\n", g);        //7printf("*p4=%d\n", *p4);  //7printf("**pp=%d\n", **pp);    //7char ch1[] = "hallo";char ch2[] = "world";const char* const p = ch1;//*(p+1) = 'e';//error//p[1] = 'e';//error//p = ch2;//errorchar** pc = &p;//*(*pp + 1) = 'e';//OK//printf("*ch1=%s\n", ch1);  //hello//printf("*ch2=%s\n", ch2);   //world//printf("p=%s\n", p);        //hello//printf("*pp=%s\n", *pp);    //hello*pc = ch2;printf("*ch1=%s\n", ch1);  //halloprintf("*ch2=%s\n", ch2); //worldprintf("p=%s\n", p);      //worldprintf("*pc=%s\n", *pc);  //world

在编辑程序时,指针作为函数参数,如果不想修改指针对应内存空间的值,需要使用const修饰指针数据类型。

7.3 指针和数组的加减运算

7.3.1 加法运算

①指针计算不是简单的整数相加;
②如果是一个int*(char*),+1的结果是增加一个int(char)的大小。

#include<stdio.h>//字符串拷贝
void stringCopy1(char string[],char destination[]){//数组操作int i = 0;while(string[i] != 0){//等价于while(string[i])destination[i] = string[i];i++;}destination[i] = 0;
}void stringCopy02(char *string, char *destination){int i = 0;while (*(string+i)){*(destination+i)= *(string+i);i++;}*(destination+i) = 0;
}void stringCopy03(char *string, char *destination){while (*string){*destination = *string;destination++;//指针+1,即数组某元素的地址+sizeof(int),指向下一个元素string++;}*destination = 0;
}void stringCopy(char *string, char *destination){while (*destination++ = *string++);//运算优先级操作顺序同void stringCopy03
}int main(){char str[] = "hello world";char dest[100];stringCopy(str, dest);printf("%s\n", dest);return 0;
}

7.3.2 减法运算

 int a[] = {1,2,3,4,5,6,7,8,9,10};int* p1;p1 = &a[3];printf("address(p1)=%p\n", p1);printf("address(a) =%p\n",  a);//p1-a=3=12/4printf("p1-a=%d\n", p1-a);//两指针相减求的是偏移量(步长)=地址差值/sizeod(类型)char* p2;p2 = &a[3];p2--;p2--;p2--;printf("address(a[3]) =%p\n",&a[3]);printf("address(p2)   =%p\n",  p2);//指针运算跟指针的数据类型有关int* p3 = &a[3];printf("a[-1]=%d\n", a[-1]);//越界//指针操作数组时下标允许是负数printf("p3[-1]=%d\n", p3[-1]);//等价于*(p3 -1),即打印a[3-1]=3int b[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int* p4;//p4 = p4 + b;//两个指针不能相加,会变成野指针//p4 = p4 * b;//两个指针不能相乘//p4 = p4 * 4;//指针不能与变量相乘除,没意义//总结:两个指针之间只能相减,指针与变量只能相加减,不可乘除求模//也不可以进行左移右移运算,其他运算操作都可以p4 = &b[3];if (p4 > b)     { printf("真\n"); }

7.3.3 指针数组

 int a[] = { 1, 2, 3 };int b[] = { 4, 5, 6 };int c[] = { 7, 8, 9 };   //指针数组,它是数组,数组的每个元素都是指针类型int* arr[] = {a,b,c};   //指针数组是特殊的二维数组int** p = arr;           //指针数组和二级指针建立关系//输出1printf("p[0][0]=%d\n",p[0][0]);  //等价于printf("%d\n",arr[0][0]);printf("*p[0]=%d\n",*p[0]);      //等价于printf("%d\n",*arr[0]);printf("**p=%d\n",**p);            //等价于printf("%d\n",**arr);//输出5//二级指针加一个偏移量相当于跳过一个一维数组//一级指针加一个偏移量相当于跳过一个元素printf("p[1][1]=%d\n",p[1][1]);//等价于printf("%d\n",  arr[1][1]);printf("*(p[1]+1)=%d\n",*(p[1]+1));//等价于printf("%d\n",*(arr[1]+1));printf("*(*(p+1)+1)=%d\n",*(*(p+1)+1));//等价于printf("%d\n",*(*(arr+1)+1));printf("指针数组大小:%d\n", sizeof(arr));            //12,指针数组里面存的是指针,大小为指针数量*4printf("一个指针的大小:%d\n", sizeof(*arr));            //4,相当于sizeof(arr[0]),指针数组里面存的是指针printf("一个整型元素的大小:%d\n", sizeof(**arr));for (size_t i = 0; i < sizeof(arr)/sizeof(arr[0]); i++){for (size_t j = 0; j < sizeof(a) / sizeof(a[0]); j++){//printf("%d  ", p[i][j]);//printf("%d  ",*(p[i]+j));printf("%d  ",*(*(p+i)+j));}puts("");}printf("address(arr) = %p\n", arr);//等价于printf("address(&arr[0]) = %p\n", &arr[0]);是二级指针printf("address(arr[0]) = %p\n", arr);//等价于printf("address(&a[0]) = %p\n", &arr[0]);或者等价于printf("address(a) = %p\n", a);是一级指针

7.4 多级指针

 int w = 1,x = 2,y = 3;//二级指针就是指向一个一级指针变量地址的指针int* array[] = {&w,&x,&y};//指针数组的每个元素都是指针for(int i = 0; i<sizeof(array)/sizeof(array[0]); i++){printf("%d  ", *array[i]);}int a = 10, b = 20;int* p = &a;int** pp = &p;*pp = &b;printf("%d\n", a);      //10printf("%d\n", b);        //20printf("%d\n", *p);       //20printf("%d\n", **pp); //20**pp = 30;printf("%d\n", a);     //10printf("%d\n", b);        //30printf("%d\n", *p);       //30printf("%d\n", **pp); //30//说明**pp == *p == a//说明*pp == p == &a;//int*** ppp = &&&a; //error,不知道一级指针跟二级指针的内容

7.5 指针和函数

7.5.1 函数形参改变实参的值

#include<stdio.h>
void swapValue(int x, int y){printf("address(x)=%p,address(y)=%p\n", &x, &y);//y地址比x高,先存y(栈区知识)int temp = x;x = y;y = temp;printf("after transform:x=%d,y=%d\n", x, y);//x=2,y=1
}void swapAddress(int* x, int* y){//指针作为参数int temp = *x;*x = *y;*y = temp;printf("after transform:x=%d,y=%d\n", *x, *y);//x=4,y=3
}int main(){int a = 1, b = 2, c = 3, d = 4;printf("address(a)=%p,address(b)=%p\n", &a, &b);//在栈区存储,a的地址比b高,且地址不相邻,防止相邻指针找到下一个地址使数据篡改swapValue(a,b); //值传递:形参不影响实参printf("after transform:a=%d,b=%d\n", a, b);//a=1,b=2swapAddress(&c,&d); //地址传递:形参影响实参printf("after transform:c=%d,d=%d\n", c, d);//c=4,d=3return 0;
}

7.5.2 数组名做函数参数

数组名做函数参数,函数的形参会退化为指针,丢失精度。

#include<stdio.h>//回顾6.3.4的void BubbleSort( )代码解释一定要传递length参数原因
void BubbleSort(int array[ ]){//假如不传递length参数,不管array[ ]括号里面的值是什么,下行代码求length的结果都是1。再从第4代码行注释可知参数int array[ ]等价于int* arrayint length = sizeof(array) / sizeof(array[0]);//假如此处直接计算lengthprintf("%d\n", sizeof(array));//数组作为函数参数的时候,变成指针变量,所以第三行代码sizeof(array)=sizeof(int*)=4,丢失数组的精度(数组元素个数)//printf("%d    ", length);//for (size_t i = 0; i < length; i++){//  for (size_t j = 0; j < length - 1 - i; j++){//        if (array[j] > array[j + 1]){       //if(*(array+j)) > *(array+j+1)//         int temp = array[j];       //int temp = *(array+j);  //          array[j] = array[j + 1];  //*(array+j) = *(array+j+1);//          array[j + 1] = temp;      //*(array+j+1) = temp;//     }               //if里面的操作全是指针,并不是数组//    }//}
}int main(){int a[] = { 9, 1, 7, 4, 2, 10, 3, 8, 6, 5 };BubbleSort(a);//a=1//for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){//printf("%d\t", a[i]);//}return 0;
}

7.5.3 指针做为函数的返回值

#include <stdio.h>//字符串中查找某字符第一次出现位置
char* findChar01(char string[],char ch){//char*或者char数组返回字符串int i = 0;while (string[i]){//返回字符型指针,也就是返回该元素对应的地址if (string[i] == ch)   { return &string[i]; }i++;}return NULL;
}char* findChar02(char *string, char ch){int i = 0;while (*(string+i)){ if (*(string + i) == ch)   { return string + i; }i++;}return NULL;
}char* findChar(char *string, char ch){while (*string){ if (*string == ch)    { return string; }string++;}return NULL;
}int main(){char str[] = "hello world";char* p = findChar(str,'l');//打印出llo worldif(p == NULL)  { printf("No Found"); }else           { printf("%s\n", p);  }return 0;
}

7.6 指针和字符串

7.6.1 字符指针

 char ch2[] = "hello world";//栈区字符串,允许修改char* p2 = "hello world";//数据区常量字符串,不允许修改char* p0 = "hello world";//数据区常量字符串,不允许修改ch2[1] = 'a';//p2[1] = 'a';//调试发现:写入位置0x00FD5859时发生访问冲突//*(p3+1) = 'a';//同上,error,原因见15行16行注释printf("%c\n", *p2);//hprintf("%s\n", ch2);//hallo worldprintf("%s\n", p2);//hello world,不是hallo worldprintf("address(p2)=%p\n", p2);//数据区常量不可修改且字符串相同printf("address(p0)=%p\n", p0);//所以p3跟p0地址相同

7.6.2 字符指针做函数参数

#include<stdio.h>
int StringLength01(char* string){if (string == NULL)      return 0;//防止主函数传过来空字符串int i = 0;while (string[i]) i++;return i;
}int StringLength(char* string){if (string == NULL)       return 0;char* temp = string;while (*temp)     temp++;return temp - string;
}int main(){char ch[] = "hello world";printf("strlen(ch)=%d", StringLength(ch));//防止参数为NULLreturn 0;
}

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

#include <stdio.h>//结合gcc编译器或者Linux
int main(int argc, char *argv[]){//argc: 传参数的个数;argv:指针数组,指向输入的参数printf("argc = %d\n", argc);//打印参数个数if (argc < 3){  //命令行输入的参数个数小于3个,提示输入参数不够printf("input parameter no enough");return -1;}for(int i = 0; i < argc; i++){   //命令行输入的参数个数大于等于3个,打印所有参数printf("%s\n", argv[i]);}return 0;
}

打开CMD,输入命令gcc -o D:\copy.exe D:\project\VS2022\Project1\Project1\project.c(命令模板是:gcc -o 生成文件 源文件),回车后生成D:\copy.exe。

问题:使用gcc编译代码是报出错误如下:

error: 'for' loop initial declarations are only allowed in C99 mode
note: use option -std=c99 or -std=gnu99 to compile your code      //使用选项-std=c99 或者 -std=gnu99(gun是标准插件)

原因:是因为gcc基于c89标准,不允许for循环中定义增量,即不支持for(int i=0; i<len; i++)
方法①-修改为C99标准库:在CMD输入命令-std=c99回车即可,就可以在for循环内定义变量。
解决方法②-修改代码:把for循环写成如下方式即可:

int i;
for(i=0; i<len; i++)

如果没有报错,则编译通过,并且生成copy.exe文件。

在命令行输入3个及其以上的参数D:\copy.exe D:\a.txt D:\b.txt(参数之间用空格分开),条件成立,输出3个参数并打印参数个数。在命令行输入2个及其以下的参数D:\copy.exe D:\a.txt,条件不成立,打印提示参数个数不足。

7.6.4 项目开发常用字符串应用模型

7.6.4.1 字符串查找字符串

#include <stdio.h>//一篇文章出现字符串的个数,先要进行字符串查找
#include <string.h>
char* findString01(char source[], char destination[]){char* ergodic = source;          //遍历源字符串指针char* record = source;           //记录相同字符串首地址char* rollback = destination;  //回滚while (*ergodic){record = ergodic;//第一次定义时时相同的,之后是回滚令其相同while (*ergodic == *rollback && *ergodic != 0){//rld\0  rld\0ergodic++;rollback++;}if (*rollback == 0)     { return record; }rollback = destination;//目标字符串更新到起始位置ergodic = record;ergodic++;}return NULL;
}char* findString(char* source, char* destination) {//返回一篇文章出现第一次字符串    char* sourceRecord = source;           //遍历源字符串指针char* destinationRecord = destination;   //遍历目标字符串指针char* rollback = source;                //回滚到源字符串与目标字符串第一次相同的地址while (*sourceRecord) {                          //循环的大前提是遍历源字符串指针不到结尾0处if (*sourceRecord == *destinationRecord) {     //当遍历源字符串字符跟遍历目标字符串字符相同时sourceRecord++;                                   //①遍历源字符串指针+1destinationRecord++;                            //②遍历源字符串指针+1if (*destinationRecord == 0) {                  //当如果遍历目标字符串到结尾0时,返回一个到源字符串与目标字符串第一次相同的地址return rollback;}}else{                                         //当遍历源字符串字符跟遍历目标字符串字符不相同时destinationRecord = destination;              //①遍历目标字符串指针返回到目标字符串首地址rollback++;                                        //②回滚指针+1sourceRecord = rollback;                     //遍历源字符串指针 = 回滚指针}}return NULL;                                        //大前提不成立,返回NULL
}//如果是char只返回一个字符int count(char source[], char destination[]) {//返回一篇文章出现字符串次数  int total = 0;                                 //计数char* p = findString(source, destination);     //定义一个char* p接收返回值if (p == NULL) { printf("No Found\n"); }      //如果为空,打印未找到while (p) {                                      //循环的大前提是返回值不为空total++;                                       //①计数+1printf("%s\n",p);                             //②打印字符串p += strlen(destination);                     //③偏移strlen(dest)个位置p = findString(p, destination);                    //④将偏移后的p作为源字符串进行新的一轮检索}return total;                                   //返回数量
}int main() {char src[] = "11ababcdcd112222abcd3333abcd4444abcd5555";//源字符串char dest[] = "abcd";                                  //目标字符串printf("count=%d\n",count(src, dest));                    //打印数量return 0;
}

7.6.4.2 统计小写字母出现次数

#include <stdio.h>
#include <string.h>char ch[] = "just something I can turn to,I want something just like this";int a[26] = { 0 };//存储26个小写字母出现的个数,并全部初始化为0for (size_t i = 0; i < strlen(ch); i++){a[ch[i] - 'a']++;//如果出现a有2次,则a[0]=2}for (size_t i = 0; i < 26; i++){if(a[i])//没有的字母不显示printf("%c appear %d times\n", i + 'a', a[i]);}//小写字母的ASCII码范围是97~97+25return 0;
}

7.6.4.3 字符串反转模型(逆置)

#include <stdio.h>
#include <string.h>
void stringReverse(char string[]) {int i = 0, j = strlen(string) - 1;while (i < j) {int temp = string[i];string[i] = string[j];string[j] = temp;i++;j--;}
}void stringReverse01(char* string) {char* begin = string, * end = string + strlen(string) - 1;while (begin < end) {char temp = *begin;*begin = *end;*end = temp;begin++;end--;}
}int main() {char str[] = "abcdefg";stringReverse(str);printf("After reverse:%s\n", str);return 0;
}

7.6.4.4 回文字符串

void palindromeString(char string[]){//回文字符串,返回类型是voidint i = 0, j = strlen(string) - 1;while (i < j){if (string[i] != string[j]){printf("the string isn't palindrome\n");return;//返回void类型需要return;表示结束子程序}i++;j--;}printf("the string is palindrome\n");
}int main(){char str1[] = "abcdcba";//奇数回文字符串char str2[] = "abccba";//偶数回文字符串char str3[] = "abcbdcba";//不是回文字符串palindromeString(str1);return 0;
}

7.6.5 字符串处理函数

7.6.5.1 strcpy()

函数名 char *strcpy(char *dest, const char *src);
头文件 #include <string.h>
参数 dest:目的字符串首地址
src:源字符首地址
功能 把src所指向的字符串复制到dest所指向的空间中,'\0’也会拷贝过去
返回值 成功:返回dest字符串的首地址
失败:NULL

注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(){char dest[10] = "123456789";char src[] = "hello world";//strcpy(dest, src);//复制到dest[10],没有遇到\0,会继续复制//printf("%s\n", dest);//缓冲溢出错误return 0;
}

7.6.5.2 strncpy()

函数名 char *strncpy(char *dest, const char *src, size_t n);
头文件 #include <string.h>
参数 dest:目的字符串首地址
src:源字符首地址
n:指定需要拷贝字符串个数
功能 把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含’\0’
返回值 成功:返回dest字符串的首地址
失败:NULL

7.6.5.3 strcat()

函数名 char *strcat(char *dest, const char *src);
头文件 #include <string.h>
参数 dest:目的字符串首地址
src:源字符首地址
功能 将src字符串连接到dest的尾部,‘\0’也会追加过去
返回值 成功:返回dest字符串的首地址
失败:NULL

7.6.5.4 strncat()

函数名 char *strncat(char *dest, const char *src, size_t n);
头文件 #include <string.h>
参数 dest:目的字符串首地址
src:源字符首地址
n:指定需要追加字符串个数
功能 将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
返回值 成功:返回dest字符串的首地址
失败:NULL

7.6.5.5 strcmp()

函数名 int strcmp(const char *s1, const char *s2);
头文件 #include <string.h>
参数 s1:字符串1首地址
s2:字符串2首地址
功能 比较 s1 和 s2 的大小,比较的是字符ASCII码大小
返回值 相等:0
大于:>0
小于:<0

7.6.5.6 strncmp()

函数名 int strncmp(const char *s1, const char *s2, size_t n);
头文件 #include <string.h>
参数 s1:字符串1首地址
s2:字符串2首地址
n:指定比较字符串的数量
功能 比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小
返回值 相等:0
大于:>0
小于:<0

7.6.5.7 sprintf()

函数名 int sprintf(char *str, const char *format, …);
头文件 #include <stdio.h>
参数 str:字符串首地址
format:字符串格式,用法和printf()一样
功能 根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 ‘\0’ 为止
返回值 成功:实际格式化的字符个数
失败: - 1
#include<stdio.h>
#pragma warning(disable:4996)
int main() {char ch1[100];sprintf(ch1, "hello\0 world");printf("%s\n", ch1);                        //hello,把内容放在ch1中printf("%d\n", sprintf(ch1, "hello world"));//11,"hello world"一共11个字符sprintf(ch1, "%02d+%03d=%04d", 1, 2, 4);printf("%s\n", ch1);                                    //01+002=0004,把格式化后内容放在ch1中printf("%d\n", sprintf(ch1, "%03d+%03d=%03d", 1, 2, 3));//11,"001+002=004"一共11个字符sprintf(ch1, "%x+%o=%d", 0xAF19, 0147, 100);printf("%s\n", ch1);                                        //不用管格式化输出内容是否错误表达式printf("%d\n", sprintf(ch1, "%x+%o=%d", 0xAF19, 0147, 100));//12,"0xAF19+147=100"一共12个字符char ch2[] = "1+2=4";int a, b, c;sscanf(ch2, "%d+%d=%d", &a, &b, &c);printf("a=%d,b=%d,c=%d\n", a, b, c);printf("%d\n", sscanf(ch2, "%d+%d=%d", &a, &b, &c));//3,"1+2=4"成功转换的值的个数一共3个,即a,b,cchar ch3[] = "hello world";char ch4[100];//sscanf(ch3, "%s", ch4);//hello,字符串空格换行直接结束sscanf(ch3, "%[^\n]", ch4);//正则表达式printf("%s\n", ch4);printf("%d\n", sscanf(ch3, "%[^\n]", ch4));//1,"hello world"成功转换的值的个数一共1个,即ch4char ch5[] = "helloworld";char ch6[100];sscanf(ch5, "%5s", ch6);//ch6读取ch5前5个字符printf("%s\n", ch6);//helloprintf("%d\n", sscanf(ch5, "%5s", ch6));//1,"helloworld"成功转换的值的个数一共1个,即ch6char ch7[100];char ch8[100];sscanf(ch5, "%5s%s", ch7, ch8);//ch7读取ch5前5个字符,ch8读取ch5剩下的printf("ch7=\"%s\",ch8=\"%s\"\n", ch7, ch8);//ch7="hello",ch8="world"printf("%d\n", sscanf(ch5, "%5s%s", ch7, ch8));//2,"helloworld"成功转换的值的个数一共2个,即ch7跟ch8return 0;
}

7.6.5.8 sscanf()

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

7.6.5.9 strchr()

函数名 char *strchr(const char *s, int c);
头文件 #include <string.h>
参数 s:字符串首地址
c:匹配字母(字符)
功能 在字符串s中查找字母c出现的位置
返回值 成功:返回dest字符串的首地址
失败:NULL

7.6.5.10 strstr()

函数名 char *strstr(const char *haystack, const char *needle);
头文件 #include <string.h>
参数 haystack:源字符串首地址
needle:匹配字符串首地址
功能 在字符串haystack中查找字符串needle出现的位置
返回值 成功:返回第一次出现的needle地址
失败:NULL

7.6.5.11 strtok()

函数名 char *strtok(char *str, const char *delim);
头文件 #include <string.h>
参数 str:指向欲分割的字符串
delim:为分割字符串中包含的所有字符
功能 来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0
返回值 成功:分割后字符串首地址
失败:NULL
注意 ①在第一次调用时,strtok()必需给予参数s字符串;
②虽然源字符串被修改\0,但在在缓冲区保留一份源字符串,往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针。
#include<stdio.h>
#include<string.h>
#pragma warning(disable:4996)
int main(){char ch1[] = "http://cet-bm.neea.edu.cn/";char* p1 = strtok(ch1, "://");//http\0printf("ch1 = %s\n", ch1);printf("address(p1): %p\n", p1);//截取第一次字符串的地址printf("address(ch1):%p\n", ch1);//跟上面地址相同printf("%s  ", p1);p1 = strtok(NULL, ".");//cet-bm\0neea.edu.cn/????printf("%s  ", p1);p1 = strtok(NULL, ".");//neea\0edu.cn/printf("%s  ", p1);p1 = strtok(NULL, ".");//edu\0cn/printf("%s  ", p1);p1 = strtok(NULL, ".");//cn/printf("%s\n", p1);char ch[] = "D://Visual Studio 2013//VC//Snippets//2052//Visual C++";char* p = strtok(ch, "//");while(p){printf("%s  ", p);//ch = strtok(NULL, "//");//ch3是不可修改的左值p = strtok(NULL, "//");}return 0;
}

7.6.5.12 atoi()

函数名 int atoi(const char *nptr);
头文件 #include <stdlib.h>
参数 nptr:待转换的字符串
功能 atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符(‘\0’)才结束转换,并将结果返回返回值
返回值 成功转换后十进制整数
注意 ①atof():把一个小数形式的字符串转化为一个浮点数;
②atol():将一个字符串转化为long类型。
 char ch1[] = "   -123456ABC";//跳过前面的空格字符char ch2[] = "   -0xABC123456ABC";//不跳过非空格字符printf("ch1=%s\n", ch1);printf("atoi(ch1)=%d\n", atoi(ch1));printf("ch2=%s\n", ch2);printf("atoi(ch2)=%d\n", atoi(ch2));printf("atoi(ch2)=%x\n", atoi(ch2));//只能识别十进制

码字不易,如果大家觉得有用,请高抬贵手给一个赞让文章上推荐让更多的人看到吧,也可以评论提出意见让后面的文章内容越来越生动丰富。

七、C/C++指针(易懂易学习,附代码案例)相关推荐

  1. 十、文件操作(易懂易学习,附代码案例)

    十.文件操作 10.1 概述 10.1.1 文件分类 10.1.2 磁盘文件的分类 10.2 文件的打开和关闭 10.2.1 文件指针FILE 10.2.2 文件的打开 10.2.3 文件的关闭 10 ...

  2. 一、C语言概述(易懂易学习,附代码案例)

    一.C语言概述 1.1 什么是C语言 1.2 为什么要学C语言 1.2.1 C语言的特点 1.2.2 C语言应用领域 1.2.3 C语言的简洁 1.3 第一个C语言程序:HelloWorld 1.3. ...

  3. Lua基础学习--附代码,运行截图

    Lua基础学习 一.lua简介 Lua [1] 是一个小巧的脚本语言.它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里 ...

  4. 【论文解读】DCN-M:Google提出改进版DCN,用于大规模排序系统的特征交叉学习(附代码)...

    " 本文结合DeepCTR-Torch中的代码实现,介绍了DCN的改进版--DCN-M.该模型能更有效地学习特征交叉,并通过低秩矩阵分解对参数矩阵进行降维,降低计算成本.受MOE结构启发,作 ...

  5. 超详细解读:神经语义解析的结构化表示学习 | 附代码分析

    在碎片化阅读充斥眼球的时代,越来越少的人会去关注每篇论文背后的探索和思考. 在这个栏目里,你会快速 get 每篇精选论文的亮点和痛点,时刻紧跟 AI 前沿成果. 点击本文底部的「阅读原文」即刻加入社区 ...

  6. 【详解】Transfer learning迁移学习 附代码

    迁移学习的训练策略: 1.先冻结卷积层只训练全链接层,这一步需要把结果最好的那个模型保存起来. 2.加载上一步保存的那个最优模型,在这个模型的基础上,再以更小的学习率训练所有层,更新网络的所有权重参数 ...

  7. slim php dd model,第二十四节,TensorFlow下slim库函数的使用以及使用VGG网络进行预训练、迁移学习(附代码)...

    在介绍这一节之前,需要你对slim模型库有一些基本了解,具体可以参考第二十二节,TensorFlow中的图片分类模型库slim的使用.数据集处理,这一节我们会详细介绍slim模型库下面的一些函数的使用 ...

  8. 独家 | 带你认识HDFS和如何创建3个节点HDFS集群(附代码案例)

    作者:尼廷·兰詹(Nitin Ranjan) 翻译:陈之炎 校对:王威力 本文约1500字,建议阅读5分钟. 在本文中,大数据专家将为您介绍如何使用HDFS以及如何利用HDFS创建HDFS集群节点. ...

  9. 五、数组、字符串以及冒泡排序--附代码案例

    五.数组和字符串 5.1 一维数组 5.1.1 一维数组的定义和使用 5.1.2 一维数组的初始化 5.1.3 数组名 5.1.4 强化训练 5.1.4.1 一维数组的最值 5.1.4.2 一维数组的 ...

最新文章

  1. 关于js中的时间——计算时间差等
  2. Leaflet中使用Leaflet.fullscreen插件实现全屏效果
  3. CCNA-第十五篇-DHCP配置+SDN介绍(最后一章)
  4. 在线看大会!就来云栖号!
  5. 使用zigbee的协议栈进行协调器路由器终端初始化
  6. 打开程序并监听程序是否退出
  7. 新疆计算机证相关信息技术,2019新疆中小学教师计算机考试资料:信息技术课程基本理念...
  8. CSS基本选择器之类选择器多类名(CSS、HTML)
  9. MariaDB ColumnStore一些限制和BUG总结
  10. AtCoder Grand Contest 003 D - Anticube
  11. 标明文献引用及文献列表自动生成(尾注交叉引用)
  12. python3实例(一)平方根
  13. js 取表格table td值 botton a
  14. 企业应该怎样选择mes系统?
  15. voxelmorph中的STN网络
  16. mac Vim/Vi Insert模式 ESC 按键无效的解决办法
  17. GDOI2017游记
  18. Address localhost:1099 is already in use
  19. 大数据技术解决 征信环节中产生的问题
  20. 万圣节头像框生成工具微信小程序源码下载支持流量主收益模式

热门文章

  1. 企业电子邮箱的企业网盘是什么?如何使用?
  2. 某公司高管疾呼:底层程序员离职率太高,有人入职不到半年就走!建议把恶意离职加入征信,让年轻人对公司有起码的尊重!...
  3. STM32F103C8T6基础开发教程(HAL库)—Keil添加注释的快捷键
  4. 一个“鸡血班”毕业生的自述
  5. android, 模拟器
  6. php用户注册系统(简单实现)
  7. 使用腾讯云函数每天定时签到领取京豆
  8. RedisCluster redis集群配置
  9. java其他框架杂记
  10. 使用anaconda 要用conda 方式更新各个软件,不要用pip