指针和字符串目录
指针和字符串
(本人记笔记特点:笔记都在代码注释里,文字说明少,详见代码内容哈)
1.指针概述
1.1内存
内存含义:
(1)存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要组成部分。
(2)内存:内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3.
(3)外存:外部存贮器,长时间保存程序/数据——掉电不丢失ROM、ERRROM、FLASH(NAND、NOR)、硬盘,光盘。
1.2物理存储器和存储地址空间
有关内存的两个概念:物理存储器和存储地址空间。
物理存储器:实际存在的具体存储器芯片
(1)主板上装插的内存条
(2)显示卡上的显示RAM芯片
(3)各种配饰卡上的RAM芯片和ROM芯片
存储地址空间:对存储器编码的范围。人们软件上常说的内存就是指存储地址空间。
(1)编码:对每个物理存储单元(一个字节)分配一个号码
(2)寻址:可以根据分配的号码找到相应存储单元,完成数据的读写。
2指针基础知识
2.1指针变量的定义和使用
指针变量=&普通变量;
*指针变量=普通变量的值;
#include<stdio.h>
int main(){
int a=12;
printf("%p\n",&a); //获取a 的地址
return 0;
}
2.2通过指针间接修改变量的值
#include<stdio.h>
int main(){
//定义指针变量存储变量地址
int a=10;
//指针类型 —> 数据类 型*
//&是取地址符号是升维度的
//*是取址符号是降维度的
int *p=&a;
*p=100; //指针p前面加*指向a
printf("%d\n",a);
printf("%p\n",&a); //地址是无符号16进制整型数
printf("%p\n",p); //结果一样
return 0;
}
2.3指针大小
在32位电脑操作系统下所有指针类型都是4个字节大小;
在64位电脑操作系统下所有指针类型都是8个字节大小;
2.4野指针和空指针
野指针:指针变量指向一个未知空间,操作野指针对应内存空间可能报错;
空指针:指针变量指向内存编号为0的空间#define NULL((void*)0),操作空指针时对应的内存空间肯定报错,在程序中用于条件判断。
#include<stdio.h>
int main(){
int *p1;
char *p2;
//*p1=12; 野指针 :指向未知空间,直接赋值时可能报错
//printf("%d",*p1);
//所有sizeof测量指针大小,得到的总是4(32位电脑)或8 (64位电脑)
printf("%d\n",sizeof(int *));
printf("%d\n",sizeof(p1));
printf("%d\n",sizeof(char *));
printf("%d\n",sizeof(p2));
return 0;
}
#include<stdio.h>
int main(){
//空指针:内存编号位0的空间
int *p=NULL;
*p=100; //错误写法
printf("%d",*p);
return 0;
}
2.5万能指针viod*
:
指针可以指向任意变量的内存空间:
可以将所有指针类型赋值给万能指针,万能指针一般用作于函数形参
#include<stdio.h>
int main(){
void*p=NULL;
printf("万能指针在内存占的字节大小%d\n",sizeof(void*)); //万能指针占8个字节
int a =10;
p=(void*)&a; //指向变量时最好转为void*
*(int*)p=100; //使用指针变量指向的内存时转为int*
printf("%d\n",a); //打印100
printf("%d\n",*(int*)p); //打印100
return 0;
}
2.6 const修饰指针
(1)const修饰的指针变量,如const int *p=&a;
则指针指向的变量a的值不可修改,而指针的指向可以修改
#include<stdio.h>
int main(){
//修饰常量
const int a=10;
//a=100; 报错 a不可修改
printf("%d\n",a); //输出10
//修饰指针
const int* p=&a;
//*p=100; 报错 p不可修改
printf("%d\n",*p); //输出10
return 0;
}
(2)const修饰指针类型:如int * const p=&a;
则指针指向的变量a的值可以修改,但指针不可指向其他变量
#include<stdio.h>
int main(){
int a=10;
int b=12;
int* const p=&a;
printf("%d\n",*p);
//int*p=&b; 报错,不可以修改指针变量
*p=100;
printf("%d\n",a); //可以修改变量的值,输出100
return 0;
}
(3)const修饰指针类型,修饰指针变量;
指针的指向不可修改,指针指向的变量的值也不可修改
二级指针理解:A(即B的地址)是指向指针的指针,称为
二级指针
,用于存放二级指针的变量称为
二级指针变量
.根据B的不同情况,二级指针又分为指向指针变量的指针和指向数组的指针。
【例子】 const修饰指针结合 二级指针应用;
通过二级指针可以实现对const修饰指针的修改
#include<stdio.h>
int main(){
//二级指针操作
int a =10;
int b =20;
const int* const p=&a;
int **pp=&p;
printf("%d\n",*p); //输出10,定量不可改
**pp=100;
printf("%d\n",*p); //通过二级指针可以修改已经指针的值输出100
*pp=&b;
return 0;
}
3指针和数组
3.1数组名
:
数组名是数组首元素地址,但它是一个常量,不可直接赋值;
数组可以当成指针使用
#include<stdio.h>
int main(){
int arr[]={1,2,3,4,5,6,7,8};
//数组名是数组首元素地址
int*p=arr; //不用取址
printf("%p\n",p);
printf("%p\n",arr); //结果一样
int i;
for(i=0;i<8;i++){
printf("%d\n",p[i]);
printf("%d\n",*(arr+i)); //加1等同于地址往后移动一个sizrof(int)
}
p++;
printf("%p\n",p); //增加4
printf("%p\n",arr);
p=arr;
for(i=0;i<8;i++){
printf("%d\n",*p++);
}
//P读取后地址不再是原来地址 ,在arr之外
printf("%p\n",p);
printf("%p\n",arr);
return 0;
}
3.2指针操作数组元素
#include<stdio.h>
int main(){
//指针相减
int arr[]={1,2,3,4,5,6,7,8,2,2,2};
int*p=arr;
int i;
for(i=0;i<8;i++){
printf("%d\n",*p++);
}
printf("p大小%d\n",sizeof(p)); //4
printf("arr大小%d\n",sizeof(arr)); //44(由数组量决定)
p++;
int step =p-arr;
printf("%d\n",step);
return 0;
}
3.3指针的加减运算
指针运算不是简单的整数相加。如果是int*指针,+1表示加一个int的大小。
指针加减的量与其数据类型有关。
#include<stdio.h>
void main0(){
//字符串拷贝
char*dest="hello world";
char*ch = dest;
int i =0;
while(*(ch+i)){
printf("%c",*(ch+i));
i++;
}
}
int main(){
int arr[]={1,2,3,4,5,6,7,8,9};
int* p;
p=&arr[3];
printf("%p\n",p);
p--;
printf("%p\n",p); //小了4
p--;
printf("%p\n",p);
p--;
printf("%p\n",p);
p=&arr[3];
int step=p-arr;
printf("%d\n",step); //输出3
char*pp;
pp=&arr[3];
printf("%p\n",pp);
pp--;
printf("%p\n",pp); //一次小了1
pp--; //p减小的量与其数据类型有关
printf("%p\n",pp);
pp--;
printf("%p\n",pp);
return 0;
}
3.4指针数组
指针数组是数组,数组每一个元素都是指针类型,示例如下:
#include<stdio.h>
int main(){
//指针数组
int*p[3];
int a=1;
int b=2;
int c=3;
int i=0;
p[0]=&a;
p[1]=&b;
p[2]=&c;
for(i=0;i<sizeof(p)/sizeof(p[0]);i++){
printf("%d\n",*(p[i]));}
return 0;
}
4.多级指针的使用:
C语言允许有多级指针存在,其中一级指针最常用,二级自传次之,三级指针基本不用。
示例:
#include<stdio.h>
int main(){
int a[] ={1,2,3};
int b[] ={4,5,6};
int c[] ={7,8,9};
int*arr[]={a,b,c};
int **p=arr;
printf("%d\n",**p+1); //打印2
printf("%d\n",*(*p+1)); //一级指针偏移,打印2,数一维数组内跳跃
printf("%d\n",**(p+1)); //二级指针偏移,打印4,跳过一个一维数组大小
**p=100;
int i,j;
for(i=0;i<9;i++){
printf("%d\n",**p+i); //输出101,102……(+i最后执行)
}
for(i=0;i<3;i++){
printf("%d\n",**(p+i)); //输出每个数组头一个
}
for(i=0;i<9;i++){
printf("%d\n",*(*p+i)); //错误,每个一维数组之间不一定跳一维指针(*p+1)
}
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf("%d\n",*(*(p+i)+j));} //一二级结合打印出全部
}
return 0;
}
5指针和函数
5.1函数形参改变实参的值
值传递:只能改变形参的值;
地址传递:地址传递可以对主函数的实参进行修改。
#include<stdio.h>
//指针传递
void swap(int*a,int*b){
int temp=*a;
*a=*b;
*b=temp;
printf("内a%d\n",*a);
printf("内b%d\n",*b);
}
int main(){
int a = 10;
int b = 20;
swap(&a,&b); //常忘,要加&
printf("主a%d\n",a); //指针传递后主函数ab值也会改变
printf("主b%d\n",b);
return 0;
}
5.2 数组名做函数参数
:
等价于指针,可以把数组当成指针使用
#include<stdio.h>
void main0(int*arr,int len) //等价于int*arr
{
//数组做函数参数是为指针.
printf("指针%d\n",sizeof(arr)); //输出8
printf("指针%d\n",sizeof(arr[0])); //输出4
//冒泡排序中的arr其实是一个指针
int i,j;
// len=sizeof(arr)/sizeof(arr[0]); //输出2
// printf("指针%d\n",len);
//通过指针形式完成冒泡排序
for(i=0;i<len-1;i++){
for(j=0;j<len-1-i;j++){
if(*(arr+j)<*(arr+j+1)){
int temp = *(arr+j);
*(arr+j)=*(arr+j+1);
*(arr+j+1)=temp;
}
}
}
for(i=0;i<len;i++){
printf("%d\n",*(arr+i));
}
printf("%d\n",*arr);
printf("%d\n",*(arr+2));
return 0;
}
int main(){
int arr[]={1,4,5,3,8,6,9,7,2};
printf("数组%d\n",sizeof(arr)); //输出36
printf("数组%d\n",sizeof(arr[0])); //输出4
int len;
len=sizeof(arr)/sizeof(arr[0]);
printf("数组%d\n",len); //输出9
main0(arr,len);
return 0;
}
5.3指针用作函数返回值
:
指针也可以用作函数返回值,这种情况下函数返回值类型需要定义为指针变量类型如:char* my_strchr,结合字符串的使用,示例如下。
【应用一】字符串中查找字符。
例子一:查找单个字符;
#include<stdio.h>
//在字符串中查找字符
char* my_strchr1(char* str, char ch) {
int i=0;
while (str[i]) {
if (str[i] == ch) {
return &str[i];
}
i++;
}
return NULL;
}
char* my_strchr2(char* str, char ch) {
while (*str) {
if (*str == ch) {
return str;
}
str++;
}
return NULL;
}
int main() {
char str[] = "hello word" ;
char* p = my_strchr1(str, 'l');
if (p == NULL) {
printf("未找到\n");
}
else {
printf("%s\n", p); //输出llo word
}
return 0;
}
【应用二】字符串中查找字符串.
例子:在字符串scr中查找字符串dest.
方法:定义3指针,2个指针一个fscr指向scr,一个tdest指向dest,用于移动;另外一个指针rscr指向scr初始位置,起标志作用,用于fscr指针归位,做下一轮判断,。
#include<stdio.h>
//字符串查找字符
char* my_strstr(char*src,char*dest){
char *fsrc =src; //一个指针遍历源字符串指针
char *rsrc =src; //一个指针记录相同字符串首地址
char *tdest =dest;
while(*fsrc){
rsrc=fsrc; //注意:用作指针不用*
while(*fsrc==*tdest&&*fsrc!='\0'){ //字符比较用*
//如果两个恰好都是\0,再执行一次while,把dest的\0用掉,就不会运行if,没有返回rsrc
fsrc++; //注意:指针移动不用*!!!!
tdest++;
}
if(*tdest=='\0'){ //能运行if即说明一一比对到dest最后一个了
return rsrc;
}
//回滚
tdest=dest; //目标字符串更新到起始位置
fsrc=rsrc; //指针更新到起始位置,并往前走一段
fsrc++;
}
return NULL;
}
int main(){
char src[]="hello worlle";
char dest[]="lle";
//若有多个lle,取第一个
char*p =my_strstr(src,dest);
if (p == NULL) {
printf("未找到\n");
}
else {
printf("%s\n", p);
}
return 0;
}
6指针和字符串
6.1字符指针
#include<stdio.h>
int main(){
char str[]="hello world"; //字符串也叫字符数组,存于栈区,可以被修改
char*p =str;
//即 char*p ="hello world"; 数据区常量区字符串
//指针p指向常量区地址
printf("%s\n",p); //打印整个字符串
printf("%c\n",*p); //p指向h 打印h 用p+i for语句
p="hello szu";
printf("%s\n",p); //打印修改后的
p[2]='m';
printf("%s\n",p); //报错不打印,p指向的字符串不可修改
return 0;
}
6.2字符串数组
使用字符串数组实现的字符串冒泡排序
#include<stdio.h>
int main()
//指针数组 里面的字符串hello可以修改
char str1[]="hello";
char str2[]="world";
char str3[]="balabala";
char*arr1[]={str1,str2,str3};
//字符串数组:给arr存放字符串地址,字符串不可修改
char* arr2[]={"dabaobei","hello","love","you"};
//让字符串实现首字母大到小冒泡排序
int i,j;
int len=sizeof(arr2)/sizeof(arr2[0]);
printf("%d",len);
for(i=0;i<4;i++){
for(j=0;j<4-i-1;j++){
if(arr2[j][0]<=arr2[j+1][0]){
char *s=arr2[j];
arr2[j]=arr2[j+1];
arr2[j+1]=s;
}
}
}
for(i=0;i<4;i++){
printf("%s\n",arr2[i]);
}
return 0;
}
【应用例题】字符串去空格
方法一:用if语句剔除空格字符;
写代码时出现错误:定义了一个野指针。最好写成数组形式。
#include<stdio.h>
#include<string.h>
void remove_space(char ch[]){
int n=strlen(ch);
char str[100]={0}; //不能写成char*str={0};是野指针 程序运行错误
int i;
int a=0;
printf("%c",ch[0]);
for(i=0;i<n;i++){
if(ch[i] != ' '){
str[a]=ch[i];
a++;
}
}
printf("%s\n",str);
}
int main(void){
char*ch="h e l lo w o r l d";
remove_space(ch);
return 0;
}
方法二:使用两个指针遍历字符串,实现字符调换;
写代码时错误:使用指针定义字符串时系统默认用const修饰指针,字符串可读不可写。应该使用字符串数组定义。
char str[]=”abc” 是栈区字符串
char *p=”abc” 是数据区常量区字符串,可读不可修改
并且记得把调换后多余重复的字符串删除。
#include<stdio.h>
#include<string.h>
void remove_space2(char ch[]) {
char* ftemp = ch; //用于遍历字符串
char* rtemp = ch;
while (*ftemp)
{ //取到0为止
if (*ftemp != ' ')
{
*rtemp = *ftemp; //数值套进
rtemp++; //地址前进
*ftemp = ' ';
}
ftemp++;
}
printf("%s", ch);
}
int main(void) {
char str[ ]= { " h e l lo w o r l d" }; //应该写成数组形式
//错误写法:char*ch="h e l lo w o r l d";
//定义字符串默认为const char*ch;
//用指针表达字符串,字符串内容只读不可写
char* ch = str;
remove_space2(ch);
return 0;
}
6.3.常用的字符串模型
strstr中的while模型:
字符串最后应该字符都是‘\0’,因此可以结合while语句,使用while(*string)的模型实现字符串的遍历,直到最后一位时while循环结束。
【应用】查找字符串出现次数
例子:在字符串”abcdnhbcabcdhjccd”查找字符串ch”abcd”出现次数。
方法使用指针做函数返回值,并用while语句判断。注意点:每一次判断后指针应该向前移动strlen(ch)的长度(即4个),避免重复,使用字符串函数时要加头文件#include<string.h>。
#include<stdio.h>
#include<string.h>
char* my_strstr(char*src,char*dest){
char *fsrc =src;
char *rsrc =src;
char *tdest =dest;
while(*fsrc){
rsrc=fsrc;
while(*fsrc==*tdest&&*fsrc!='\0'){
fsrc++;
tdest++;
}
if(*tdest=='\0'){
return rsrc;
}
//回滚
tdest=dest;
fsrc=rsrc;
fsrc++;
}
return NULL;
}
int main(){
char str[]="abcdnhbcabcdhjccd";
char ch[]="abcd";
int count=0;
char*p=my_strstr(str,ch);
while(p!=NULL){
count++;
p+=strlen(ch); //p向前移动剔除第一个abcd
p=my_strstr(p,ch); //注意此处()改为p
}
printf("%d\n",count); //打印2
return 0;
}
【延申】字符串统计出现个数
方法:字符串和数组结合。
#include<stdio.h>
#include<string.h>
int main(){
char str[]="ddbaacbaadcdcdkcnsjdncijzxnjznchqbcykkikikccccddbcdc";
int arr[26]={0}; //存放的数据是 “个数”
//arr['h'-'a'] 即arr 中h对应的个数
int i=0;
for(i=0;i<strlen(str);i++){ //wow
arr[str[i]-'a']++;
}
for(i=0;i<sizeof(arr)/sizeof(arr[0]);i++){
if(arr[i]!=0){
printf("字母%c出现次数为%d\n",i+'a',arr[i]);}
}
return 0;
}
6.4字符串处理函数
注意:使用字符串函数时都应该加头文件#include<string.h>
1、strcpy()
功能:把src所指向的字符串复制到dest所指向的空间中,
‘\0’也会拷贝过去
参数:src:原字符串首地址;dest:目的字符串首地址
返回值:成功:返回dest字符串的首地址
失败:NULL
注意:如果参数dest所指向的内存空间不够大,可能会造成缓冲溢出的错误情况
示例:
#include<stdio.h>
#include<string.h>
int main(){
char str1[10] ="";
char str2[10] ="abcdefg";
strcpy(str1,str2); //将str2复制到str1
printf("%s",str1); //打印str1得到"abcdefg"
return 0;
}
2、strcpy()
功能:把src所指向的字符串的前n个字符复制到dest所指向的空间中,
是否拷贝结束符看指定的长度是否包含’\0′
参数:src:原字符串首地址;dest:目的字符串首地址;n:指定拷贝字符串的个数
返回值:成功:返回dest字符串的首地址
失败:NULL
示例
#include<stdio.h>
#include<string.h>
int main(){
char str1[10] ="";
char str2[10] ="abcdefg";
strncpy(str1,str2,3); //将str2前三个复制到str1
printf("%s",str1); //得到"abc"
return 0;
}
3、strcat()
功能:把src字符串连接到dest的尾部,
‘\0’也会追加过去
参数:src:原字符串首地址;dest:目的字符串首地址;
返回值:成功:返回dest字符串的首地址
失败:NULL
示例
#include<stdio.h>
#include<string.h>
int main(){
char str1[10] = "sdc";
char str2[10] = "sss";
strcat(str1,str2); //strcat 拼接函数
printf("%s",str1); //打印sdcsss
return 0;
}
4、strncat()
功能:把src字符串前n个字符连接到dest的尾部,
‘\0’也会追加过去
参数:src:原字符串首地址;dest:目的字符串首地址;n:追加字符个数
返回值:成功:返回dest字符串的首地址
失败:NULL
示例
#include<stdio.h>
#include<string.h>
int main(){
char str1[10] = "sdc";
char str2[10] = "sss";
strncat(str1,str2,1); //strcat 拼接函数
printf("%s",str1); //打印sdcs
return 0;
}
5、strcmp()
功能:比较s1和s2的大小,比较的是字符ASCII的大小
参数:s1:字符串1的首地址;s2:字符串2的首地址;
返回值:相等:0;
大于:>0不同操作系统下strcmp()结果不同,返回ASCII差值
小于:<0
6、strncmp()
功能:比较s1和s2的大小,比较的是字符ASCII的大小
7、sprintf()
功能:根据参数format字符串来转换并格式化数据,然后将结果输入到str指定空间中,直到字符串结束符’\0’为止。
同理:使用sscanf()输出
参数:str:字符串首地址;format:字符串格式,用法和printf()一样;
返回值:成功:实际格式化的字符串个数;
失败:-1;
示例
#include<stdio.h>
#include<string.h>
int main(){
char dest[100]={0};
sprintf(dest,"aaa"); //把aaa放入字符串中
printf("%s\n",dest); //输出aaa
sprintf(dest,"%d+%d=%d",1,2,3);
printf("%s\n",dest); //输出1+2=3
sprintf(dest,"%x+%o=%d",0x123,1222,3);
printf("%s\n",dest); //输出123+2306=3
return 0;
}
8、strchr()
功能:在字符串s中查找字母c出现的位置
参数:s:字符串首地址;c:匹配字母(字符)
返回值:返回第一次出现c的地址;
9、strstr()
功能:在字符串s1中查找字符串s2出现位置;
参数:s1:源字符串首地址;s2:匹配字符串首地址;
返回值:返回第一次出现s2的地址;
10、strtok()
功能:将字符串分割成一个个片段,当strtok在参数str字符串中发现参数delim中包含的分割字符时,则会将该字符改为’\0’字符,当连续出现多个时,只替换第一个为’\0′.
参数:str:被分割的字符串
delim:为分割字符串中包含的所有字符
返回值:分割后字符串首地址
示例:注意:字符串截取会破坏原字符串
#include<stdio.h>
#include<string.h>
int main(){
char str[50]="aass.cc.dd.dcm.ddd";
char*p=strtok(str,"ss");
printf("%s\n",p); //打印aa
printf("%s\n",str); //打印aa
return 0;
}
11.atoi()
功能:atoi()会扫描str字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符’\0’才结束转换,并将结果返回返回值。
参数:str:待转换字符串
返回值:成功转换后的整数
类似的函数有:
atof():把一个小数形式的字符串转换为一个浮点数
atol():把一个字符串转化为一个long类型