目录
一句话区分p, *p, &p
&p表示编译器为变量p本身分配的内存地址,而p是一个指针变量,它指向的内存地址就用p表示,p指向的地址中的内容就用*p表示。
注: p是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数。
*p 表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。
& 是取地址运算符,&p就是取指针p的地址。
1.函数只用一个返回值(单一出口原则),结构清晰,便于修改
2.一个变量既是循环遍历的变量又是条件判断的变量(一专多能是不好的代码)
3.线性搜索没有效率,二分法效率极高,次数为log2 n
4.数组的大小
sizeof给出整个数组所占据的内容的大小,单位是字节
sizeof(a[0]给出数组中单个元素的大小,于是相除就得到了数组的单元个数
//这样的代码,一旦修改数组中初始的数据,不需要修改遍历的代码(安全)
int a[]={0,1,2,3,5};
int length = 0;
length = sizeof(a) / sizeof(a[0]);
printf("main_数组的长度为: %d\n",length);
// 所有数据的字节数除以一个数据的字节数即为数据的个数
数组作为函数参数时,往往需要传入另一个参数来传入数组的大小
此时不能再用sizeof来计算数组的元素个数,另外在参数数组中
如search(int key,int a[ ])里的[ ]中给出数组大小无效
注:传入函数参数表的数组其实是一个指针
(所以应该直接传值,如:sum(a,length);)
//不能在sum()函数里使用,
其中传递的数组参数和常量指针一样,如:
sizeof(a)=4字节//实际上是在32位编译环境的int型一个指针大小
(32位操作系统,一个字节刚好是8位,因此是4个字节)
即sizeof(a)==sizeof(int*)
函数参数表中的数组实际上是指针
sizeof(a) == sizeof(int *)
但是可以用数组的运算符[ ]进行运算
以下四种函数原型是等价的:
int sum(int *a,int n);
int sum(int *,int );
int sum(int a[],int n);//作为参数表出现的话与第一个*a是等价的
int sum(int [],int );
总结:
sizeof(数组名):返回数组所有元素占有的内存空间字节数。
sizeof(指针) :返回计算机系统的地址字节数,如果是32位系统,返回4,16位系统返回2。
5.&边上必须是明确的变量
&(a+b)不可取(错误格式)
内存是堆栈分配,自小变大的,比如int类型
int a[10];
a[0]0x000004与a[1]0x000008相差4
6. int* p,q; 与int *p,q;表达意思一样
其中p(星p)为指针,q为int型变量
指针变量不能放实际值,只放别的(整型)变量的地址
7.数组变量是特殊的指针
(1)数组变量本身表达地址,所以
int a[10];
int *p=a;//不需要用&取地址
但数组的单元表达的是变量,需要用&取地址
如: a==&a[0]
(2) [ ]运算符可以对数组做,也可以对指针做
p[0]<==>a[0]
{
int *p=&min;
//min为该数组最小值地址(由小到大排列)
*p==p[0]//两者值相等
}
(3)访问那个地址上的变量*
在C 语言中*号有三个用途,分别是:
乘号,用做乘法运算,例如5*6。
申明一个指针,在定义指针变量时使用,例如int *p;。
间接运算符,取得指针所指向的内存中的值,例如printf(“%d”,*p);。*是一个单目运算符,用来访问指针的值所表示的地址上的变量
*运算符可以对指针做,也可以对数组做:
(表示一个指针数值)
*a=25;//可以读、写值
(4)数组变量是const(常量)的指针,所以不能被赋值
如:
int b[ ]; b=a;
或者int b[ ]=a;
都是行不通的
数组变量之间是不能直接赋值或相互赋值的
int b[] <==>int * const b//常数不能被赋值
int *q=a; //可以赋值
(5)作为参数的指针
void f(int *p);
int i=0;
//在被调用的时候得到了某个变量的地址
f(&i);
//在函数里面可以通过这个指针访问外面的这个i;
eg1
int a[] = {5, 15, 34, 54, 14, 2, 52, 72};
int *p = &a[1];
p[2]的值是54
注:由0开始,从左往右,则a[1]=15;
即p[0]=15;往右从零开始数2位得出p[2]=54
eg2
int a[] = {5, 15, 34, 54, 14, 2, 52, 72};
int *p = &a[5];
p[-2]的值是54
注:由0开始,从左往右,则a[5]=2;
即p[0]=2;往左从零开始数2位得出p[-2]=54
eg3
int a[] = {0};
int *p = a;
注:其中p == &a[0]与*p == a[0]与p[0] == a[0]表达式结果均为真
8.字符计算
‘a’=97 ~ ‘z’=122
‘A’=65 ~ ‘Z’=90‘a’-'A’可以得到两段之间的距离(32)
a+ ‘a’-'A’可以把一个大写字母变成小写字母(+32)
a+ ‘A’-'a’可以把一个小写字母变成大写字母(-32)
9.逃逸字符(转义字符、逃脱字符)
定义:用来表达无法印出来的控制字符或特殊字符,它由一个反斜杠“\”开头,
后面跟上另一个字符,这两个字符合起来,组成了一个字符
字符 | 意义 |
---|---|
\b | 回退一格 |
\t | 到下一个表格位 |
\n | 换行 |
\r | 回车 |
\’ | 单引号 |
\" | 双引号 |
\\ | 反斜杠本身 |
注:
1." \t "不代表固定的字符的数量,而代表每行输出的固定位置
一个 \t 使输出从下一个制表位开始,可使上下两行对齐
2.回车换行是两个特殊的逃逸字符,源自打字机的动作
特别注意:在C语言中 \r 一般用不到, \n 表示回车换行两个动作
10.字符串
char word[]={'H','e','l','l','o','!'};
//是字符数组,不是C语言的字符串,因为不能用字符串的方式进行运算
char word[]={'H','e','l','l','o','!','\0'};
<==>
char word[]={'H','e','l','l','o','!',0};
//结尾为0,便成了字符串
(1)以0(整数0)结尾的一串字符
(0或’\0’为16进制的0X30,即为10进制的48,但是和字符’0’不同)
(2)0标志字符串的结束,但它不是字符串的一部分
(计算字符串长度的时候不包含这个0)
(3)字符串以数组的形式存在,以数组或指针的形式访问(更多的是以指针的形式)
(4)string.h里有很多处理字符串的函数
定义字符串变量
//结尾自动补0
char *str="Hello";
char word[ ]="Hello";
char line[10]="Hello";
字符串常量
“Hello”
"Hello"会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
两个相邻的字符串常量会被自动连接起来
如:
总结
1)C语言的字符串是以字符数组的形态存在的
则不能用运算符对字符串做运算
通过数组的方式可以遍历字符串
2)唯一特殊的地方是字符串字面量,可以用来初始化字符数组,表现出C语言懂这个东西(字符串)
以及标准库提供了一系列字符串函数注: 字符串字面量(string literal)是指双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以有很多个字符。
11.字符串常量
char *s = "Hello,World";
s是一个指针,初始化为指向一个字符串常量
由于这个常量所在的地方,所以实际上s是const char * s,但是由于历史的原因,编译器接受不带const的写法
但是试图对s所指的字符串做写入会导致严重的后果
如果需要修改字符串,应该用数组:
char s[] = "Hello,world!";
eg
#include<stdio.h>
int main()
{
int i=0;
char *s = "Hello World";//指针,要指向某个地方的字符串
//s[0]='B';
char *s2 = "Hello World";
char s3[] = "Hello World";//数组,字符串就在我这里
printf("&i=%p\t &i=%#x\n", &i, &i);
/*证明本地变量和s*所指的变量不在一个地方,相差很远*/
printf("s=%p\t s=%#x\n",s,s);
printf("s2=%p\t s2=%#x\n",s2,s2);
printf("Here!s[0]=%c\n", s[0]);
/*指针s,s2指向实际存放"Hello World"的地方,在只读代码段,
如s[0]='B'操作系统会起保护机制,会使程序崩溃*/
printf("s3=%p\t s3=%#x\n", s3, s3);
//在一个很大的地方,和本地变量一样,说明在本地变量那里
s3[0] = 'B';
printf("Here!s3[0]=%c\n", s3[0]);
/*把不可写的代码内容,拷贝到s3里面去,此时就可以修改字符串*/
return 0;
}
运行截图:
问:字符串到底应该写成指针还是数组?
答:
char *str = "Hello";
char word[] = "Hello";
数组:这个字符串在这里作为本地变量,则空间自动被回收
指针:这个字符串不知道在哪里
1)处理参数(只读不写)
2)如果数组作为函数的参数,实际上和指针是一样的,用指针来表达函数的参数,结果进来的都是指针
3)动态分配空间(用指针)
总结
如果要构造一个字符串用数组
如果要处理一个字符串用指针注:char*是字符串这个说法不对(不确切)
字符串可以表达为char*的形式
char*不一定是字符串(比如,char*表示指针指向的是字节,或一串连续的字节,)1)本意是指向字符的指针,可能指向的是字符的数组(就像int*一样)
2)只有它所指的字符数组有结尾的0,才能说它所指的是字符串
12.字符串计算
char *t ="title";
char *s;
s = t;
(1)并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
char string[8];
scanf("%s",string);
printf("%s",string);
scanf读入一个单词(到空格、tab或回车为止)
注:scanf是不安全的,因为不知道要读入的内容的长度
(2)安全的输入
scanf("%7s",string);
//告诉scanf最多只能读取7个字符(可以不读满)
(3)在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一,下面的内容(超过这个数字之后的数)会交给下一个%s或其他的scanf来阅读。
(4)常见错误
char *string;
/没有初始化
scanf("%s",string);
以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了由于没有对string初始化为0,所以不一定每次运行都出错(出现在本机上运行无错,拷贝到别人电脑上运行出错的情况)
(5)空字符串
char buffer[100]="";
//是一个空的字符串,是有效的字符串这是一个空的字符串,
buffer[0] == '\0'
char buffer[]="";
这个数组的长度只有1!只有buffer[0]是有效的,即
buffer[0] == '\0'
,而buffer[1] 就没有了,所以这个buffer里放不下任何字符串
13.字符串函数string.h
strlen
size_t strlen(const char *s);
//此处const为保证不会修改字符串
返回s的字符串长度(不包括结尾的 \0 )
代码:
#include<stdio.h>
#include<string.h>
size_t mylen(const char* s)
{
int idx = 0;
while(s[idx] != '\0') {
idx++;
}
return idx;
}
int main(int argc, char const *argv[])
{
char line[]="Hello";
printf("strlen=%lu\n",mylen(line));
printf("sizeof=%lu\n",sizeof(line));
return 0;
}
运行截图:
strcmp
int strcmp(const char *s1,const char *s2);
比较两个字符串(并比较出谁大谁小),返回:0: 即s1 == s2
1: 即 s1 > s2 (并给出差值,如32)
-1: 即s1 < s2 (并给出差值,如-32)
代码:
#include<stdio.h>
#include<string.h>
size_t mycmp(const char* s1,const char* s2)
{
//数组法:
// int idx = 0;
// while(s1[idx] == s2[idx] && s1[idx] != '\0') {
// idx++;
}
//return s1[idx] -s2[idx] ;
//指针法
while( *s1 ==*s2 && *s1 != '\0'){
s1++;
s2++;
}
return *s1 - *s2;
}
int main(int argc, char const *argv[])
{
char s1[]="abc";
char s2[]="Abc";
printf("%d\n",mycmp(s1,s2));
printf("%d\n",'a'-'A');
return 0;
}
运行截图:
strcpy
char *strcpy(char *restrict dst,const char *restrict src);
把第二个参数src里的字符串拷贝到第一个参数dst所表达的的空间里面去restrict表明src和dst不重叠,(C99)
最后会返回dst
为了能连起代码来(结果能参与运算)
复制一个字符串
char *dst=(char*)malloc(strlen(src) + 1);
strcpy(dst,src);
代码:
#include<stdio.h>
#include<string.h>
char* mycpy( char* dst, const char* src)
{
//数组法
//int idx = 0;
//while (src[idx]) {
// dst[idx] = src[idx];
// idx++;
//}
//dst[idx] = '\0';
//return dst;
//指针法
char* ret = dst;//最初dst的位置记录为ret,以便返回
while (*src != '\0') {
*dst = *src;
dst++;
src++;
}
//这个空循环与上面while函数代码等价(笔者不推荐萌新使用)
//while(*dst++ = *src++)
// ;
*dst = '\0';
//循环里与src对比到最后一位\0时会退出,此时src最后一位\0需要手动赋值给dst
return ret;
}
int main(int argc, char const *argv[])
{
char s1[] = "abc";
char s2[] = "EDF";
printf("s1=%s\ts2=%s\n", s1, s2);
mycpy(s1, s2);
printf("s1=%s\ts2=%s\n", s1, s2);
return 0;
}
运行截图:
strcat
char *strcat(char *restrict s1,const char *restrict s2);
把s2拷贝到s1的后面,接成一个长的字符串
返回s1
s1必须具有足够的空间
安全问题
strcpy和strcat都可能出现安全问题
如果目的地没有足够的空间,越界了也不知道
安全版本
char *strncpy(char *restrict dst,const char *restrict src,size_t n);
限定多少字符,多了掐掉,安全的不会越界
char *strncat(char *restrict s1,const char *restrict s2,size_t n);
int strncmp(const char *s1,const char *s2,size_t n);
不是为了安全,只判断前n个
字符串中找字符
char *strchr(const char *s,int c);
strchr() 函数会依次检索字符串 s 中的每一个字符,直到遇见字符 c(一个无符号字符),或者到达字符串末尾(遇见\0)。
从左往右,在字符串中寻找c第一次出现的位置,返回的是指针
char *strrchr(const char *s,int c);
strrchr() 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。
从右往左,在字符串中寻找c第一次出现的位置,返回的是指针
返回NULL表示没找到
示例代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc, char const *argv[])
{
char s[] = "hello";
char *p = strchr(s,'l');//第一个l及后面的数组
printf("第一个l及后面的代码=%s\n", p);
char *p1 = strchr(p+1, 'l');//第一个l及后面的数组
printf("第二个l及后面的代码=%s\n", p1);
char *t = (char*)malloc(strlen(p) + 1);//动态分配内存
strcpy_s(t, strlen(p)+1, p);
//将p中字符串复制到t,最后一个空间为'\0'结束符
printf("目标字符后面的代码=%s\n",t);
free(t);//记得释放空间!!!
//小技巧:取指针指向位置前面的字符串
char c = *p;
*p = '\0';
//此时数组s里的字符串只剩下指针p指向位置前面的字符串
printf("%c\n",*p);
char *t1 = (char*)malloc(strlen(s) + 1);
strcpy_s(t1, strlen(s) + 1, s);
//将s中字符串复制到t1
printf("目标字符前面的代码=%s\n", t1);
*p = c; //使用完后恢复
printf("%c\n", *p);
free(t1);
return 0;
}
运行截图:
小技巧原理图解(记得改完要恢复函数!!!)