七、C/C++指针(易懂易学习,附代码案例)
七、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++指针(易懂易学习,附代码案例)相关推荐
- 十、文件操作(易懂易学习,附代码案例)
十.文件操作 10.1 概述 10.1.1 文件分类 10.1.2 磁盘文件的分类 10.2 文件的打开和关闭 10.2.1 文件指针FILE 10.2.2 文件的打开 10.2.3 文件的关闭 10 ...
- 一、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. ...
- Lua基础学习--附代码,运行截图
Lua基础学习 一.lua简介 Lua [1] 是一个小巧的脚本语言.它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里 ...
- 【论文解读】DCN-M:Google提出改进版DCN,用于大规模排序系统的特征交叉学习(附代码)...
" 本文结合DeepCTR-Torch中的代码实现,介绍了DCN的改进版--DCN-M.该模型能更有效地学习特征交叉,并通过低秩矩阵分解对参数矩阵进行降维,降低计算成本.受MOE结构启发,作 ...
- 超详细解读:神经语义解析的结构化表示学习 | 附代码分析
在碎片化阅读充斥眼球的时代,越来越少的人会去关注每篇论文背后的探索和思考. 在这个栏目里,你会快速 get 每篇精选论文的亮点和痛点,时刻紧跟 AI 前沿成果. 点击本文底部的「阅读原文」即刻加入社区 ...
- 【详解】Transfer learning迁移学习 附代码
迁移学习的训练策略: 1.先冻结卷积层只训练全链接层,这一步需要把结果最好的那个模型保存起来. 2.加载上一步保存的那个最优模型,在这个模型的基础上,再以更小的学习率训练所有层,更新网络的所有权重参数 ...
- slim php dd model,第二十四节,TensorFlow下slim库函数的使用以及使用VGG网络进行预训练、迁移学习(附代码)...
在介绍这一节之前,需要你对slim模型库有一些基本了解,具体可以参考第二十二节,TensorFlow中的图片分类模型库slim的使用.数据集处理,这一节我们会详细介绍slim模型库下面的一些函数的使用 ...
- 独家 | 带你认识HDFS和如何创建3个节点HDFS集群(附代码案例)
作者:尼廷·兰詹(Nitin Ranjan) 翻译:陈之炎 校对:王威力 本文约1500字,建议阅读5分钟. 在本文中,大数据专家将为您介绍如何使用HDFS以及如何利用HDFS创建HDFS集群节点. ...
- 五、数组、字符串以及冒泡排序--附代码案例
五.数组和字符串 5.1 一维数组 5.1.1 一维数组的定义和使用 5.1.2 一维数组的初始化 5.1.3 数组名 5.1.4 强化训练 5.1.4.1 一维数组的最值 5.1.4.2 一维数组的 ...
最新文章
- 关于js中的时间——计算时间差等
- Leaflet中使用Leaflet.fullscreen插件实现全屏效果
- CCNA-第十五篇-DHCP配置+SDN介绍(最后一章)
- 在线看大会!就来云栖号!
- 使用zigbee的协议栈进行协调器路由器终端初始化
- 打开程序并监听程序是否退出
- 新疆计算机证相关信息技术,2019新疆中小学教师计算机考试资料:信息技术课程基本理念...
- CSS基本选择器之类选择器多类名(CSS、HTML)
- MariaDB ColumnStore一些限制和BUG总结
- AtCoder Grand Contest 003 D - Anticube
- 标明文献引用及文献列表自动生成(尾注交叉引用)
- python3实例(一)平方根
- js 取表格table td值 botton a
- 企业应该怎样选择mes系统?
- voxelmorph中的STN网络
- mac Vim/Vi Insert模式 ESC 按键无效的解决办法
- GDOI2017游记
- Address localhost:1099 is already in use
- 大数据技术解决 征信环节中产生的问题
- 万圣节头像框生成工具微信小程序源码下载支持流量主收益模式