1字节=8bit
null,编号为0的内存地址不能取运算
即null内存地址不允许进行访问
null(悬空指针)用于判断一个指针变量是否可以取
操作系统有大端和小端之分
网络中用大端,我们用小端存储
大端:多字节数据,低地址存储高字节数据 高地址存储低字节数据0x12~0x78
小端:多字节数据,低地址存储低字节数据,高地址存储高字节数据 0x78~0x12
int num = 0x12345678;//十六进制0x
char *pc = (char *)&num // 强制类型转换,这个转化是因为一个num是int 一个是char
printf(%x,*pc);//%x是读取16进制的数字
pc =(char * ) & num;
printf(%x, * pc); //答案返回 78,因为类型是char,所以只取一个字节,即8个二进制位,即两个16进制位,但是因为我们的系统是小端的,所以去78而不是12
printf(%x, * (pc+1)) //返回56,因为+1相当于移位一个单位,该单位是char是一个字节,这是十六进制的,一个位数是4个二进制,一字节是8二进制位,所以两个位数。
数组元素的访问:
数组名直接就是地址,所以不用带&
int *p=arr
下面是取值的一系列写法
arr[i]
i[arr]
*(arr+i)
*(i+arr)
p[i]
i[p]
*(p+i)
作为函数的返回值类型(需要注意一些问题)
double avg(int *arr,int len){
int sum=0;
for(i=0;i<len;i++){
sum += arr[i];
}
return 1.0*sum/len;//因为函数类型是double,所以要*1.0
}
int main(){
int arr[] ={1,9,3,2,4,8,6,10,12};
pritnf(%g,avg(arr,sizeof(arr)/sizeof(arr[0]));
}//%g是根据结果自动选择科学记数法还是一般的小数记数法
//求数组元素的平均值,一般函数的返回结果只能有一个,
//如果要多个结果,则要指针参数
返回平均值,最大值,最小值
void func(int *arr,int len,double *avg,int *maxi,int *mini)
if(avg==null || maxi == null || mini==null)//如果里面有空指针
return;//直接中断函数的方法
int sum =0;
*maxi=0;//maxi的值设置为0,即arr[i]的i设为0,把0最作为初始最大值
*mini=0;
int i;
for(i=0;i<len;i++){
sum += arr[i];
if(arr[i] > arr[*maxi]){
*maxi = i;
}
if(min(arr[i]<arr[*mini]){
*mini=i;
}
*avg = 1.0*sum/len
}
}
int main(){
double avg=0;
int maxi=0;
int mini=0;
func(…)
return 0;
}
需要注意的问题:
函数返回指针时,不能返回局部变量的地址(函数调用之后,该地址会被回收)
局部变量:在函数内部定义的变量 + 形参列表的元素
可以返回全局变量的地址,也可以返回堆内存地址
int *func(){//int* func();//func()函数返回的是一个int类型的指针
int a = 10;
printf(%p,&a);//但是在函数中不能返回局部变量的地址,当一个函数调用之后,该函数所用的内存会被回收,所以这种是错误的
return &a;
}
void bar(){
double d=3.14
printf(%g,d)
}
int main(){
int *p=func()
printf(%p,p)
print(%d,*p)
}
万能指针:void *p
指针即内存地址,即数值编号
int a = 10;
char *pc =&a//指针类型不兼容,所以会有强制转换(char *)&a
void *p = &a//万能指针 能够保存任意一个内存地址,这个方法就是不用管它是int,double等类型
但是万能指针不能取*,即不能取值。
万能指针变量能够保存一个地址,但由于是void,所以失去了该地址的类型,所以不能取*
int a=*(int *)p;//所以只有通过强制类型转换以后才能用万能指针保存的地址查到值。
二级指针:
int a =10;
int *p=&a;
//指针变量p也是变量,所以他也有地址
printf(%p,&p)//会返回一个地址
二级指针是一级指针变量的内存地址
二级指针 int **pp = &p;//定义二级指针
printf(%p,&pp)//二级指针pp存储的是一级指针变量的地址
printf(%p,*pp)// pp =&p *pp=*&p = p
printf(%p,p)//p=&a
所以这两个返回的都是a的地址
下面是都返回a的值:
printf("%d",**pp)//**pp == *p = *&a ==a
printf("%d",*p);
printf("%d",*&a);
printf("%d",a);
交换pa,pb的值,即交换地址
void swap(int **ppa,int **ppb){
//交换 *ppa *ppb 的值
int *pt = *ppa; //这个不是野指针,和一级指针的时候情况不同,*ppa是一个有用的内存地址,所以*pt保存的是一个地址
*ppa=*ppb;
*ppb = pt;
}
int main(){
int a =10,b=20;
int *pa=&a,*pb=&b;
swap(&pa,&pb);//传递了pa,pb的地址,所以函数部分的形参是二级指针
}
字符串
本质:在c语言中,没有字符串这种基本数据类型,
定义:在内存中,一串连续且以’\0’为结尾标识的字符,称为字符串
‘\0’ == ascii 0
字符串的表现形式:
字面值字符串 字符串字面值:
用””引起来的一串字符 末尾默认有’\0’的存在
这种不能修改字符串的字面值(即把hello改成Hello之类的),存储在代码区的
“hello world”
字面值完全相同的字符串地址相同,即内存中只存储一份
连续的字面值字符串自动合并为一个字符串
printf(“%s”,"hello ""world")=“hello world”
printf(%c,"hello"[0]) //返回h
”hello”[0]=’h’ // 不能修改,这是错误的,字符串字面值只能读
字符数组
char str[10]={‘h’,…,’\0’};//不写’\0’系统自动给,单字符是' '(单引号)
char str[5] = {‘h’,’e’,’l’,’l’,’o’} //这不是一个合法的字符串,没有’\0’的位置
但是不会显示语法错误,系统不会报错
字符数组要确保有足够的内存来存储’\0’,所以我们在设置数组来存储字符时,我们可以超量设置
即char[200]="hello",远超实际的长度,因为要有一个位置'\0'
存储在栈区,允许修改 str[0]=’h’
字符数组初始化
char str[10]="hello"这也是初始化的方法
char s2[5]="hello";//这样编译时候会报错,和上面那种初始化有区别,数组长度不够,只能存5个字节,但是hello有6个字节(加上了'\0')
printf(%u,sizeof("hello")); //返回6,因为连写的时候“hello”里面带了’\0’所以6字节
s2="world";//这样不行,会报错
上述两种方式,只能在定义时初始化,一旦定义后,就不能再对整体赋值
如果需要对字符串内存进行操作,一般都是用字符数组
字符指针 char*
字符指针变量不会保存字符串本身,只是保存字符串开始位置
既可以保存字面值字符串的首地址,也可以保存字符数组的首地址
char *p1=”hello world”;//字符指针指向,字面值字符串
printf(%s,p1)//就能输出hello world,%s是字符串的占位符
//字符串是连续的,所以指针只保存一个字符的地址,还是能输出一整个字符串
printf("%c",*p1) //取值的话就只能取到一个字符 h,%c是字符的占位符
从某个内存地址开始输出字符,直至遇到'\0'结束,停止输出
从某个内存地址开始到'\0'看做一个字符串
*p1='h'还是不允许,因为只读,不能修改,这意味着p1指向代码区,即字面值
char s[] = “hello world”//字符数组
长度:12 最后还有一个'\0'
char *p1=s;//设置字符指针指向字符数组 s== &s[0] 第一个字符的地址
*p1='h';//这样就可以了,所以字符串数组是可以修改的,字面值是不能修改的
printf("%u",sizeof(‘a’) //4 ’a’ 相当于ascii运算即int类型运算 ,所以 4
char c=’a’;
printf("%u",sizeof(c));//返回1
sizeof(c+1);//4 c+1 是 int
sizeof(++c) //1 相当于 c =c+1 而左边是char,所以是1
char *p = ‘hello’ ;
printf("%u",sizeof(p)); //4 因为是指针
char str[10] =’hello’;
printf("%u",sizeof(str)); //str数组 所以是数组内存大小,10个char=10
char s[]=’hello’;
prinf("%u",sizeof(s)); //这个时候数组由字符串长度+'\0'决定,所以是6
char *s = "hello world"; //保存字符串首地址
while(*s != ’\0’){
printf("%c",*s);
s=s+1;//指针+1,偏移一个单位的内存 ,因为是char,所以1个
}//遍历字符串
//s上面遍历完后指向字符串末尾
s = "hello world";
for(int i=0;s[i]!=’\0’;i++){
printf("%c" ,s[i]);
}
strlen()函数//求字符串长度
#include<string.h>//使用strlen()函数
#include<assert.h>//使用assert()函数,用来判断是否是空指针
我们需要知道系统是如何实现 strlen函数的功能
size_t mystrlen(const char *s)//size_t就是unsigned int
assert(s!=null);//宏函数,断言 s这个指针!=null
size_t len=0;
while(s[len])!=’\0’)
++len;
//或者for(len=0;*(s+len) != '\0';len++);//没有循环体也行
//for(len=0;*s!=’\0’;s++,len++);//s++指针不断指向下一个字符的地址,
return len
char *p= "hello world"
char str[20] = "hello java"
char s[20] = {‘h’,’e’,’l’,’l’,’o’};
printf("%u",strlen(p));
printf("%u",strlen(str))
printf("%u",strlen(s));//strlen就是除去'\0'的一种字符串计数方法,sizeof()计数时会记上'\0'
char *p = "hello";
printf("%u",strlen(p));//这是5,不是按sizeof算指针数1
char str[10]="hello";
这个还是5,不是按sizeof(str)算数组长度char1*10
数组传到函数时,用strlen不会和前面学的一样因为传的是地址所以4,而是计算字符长度为5
sizeof与strlen的区别一定要搞清楚:
sizeof是c语言的关键字,用于求变量或者类型数据的字节宽度
strlen是c语言string.h头文件中的一个函数,用于计算字符串的长度
字符串拷贝
先要给一个字符串里赋值
char str1[10]="hello"; //’\0’后面的内存不需要进行拷贝
char str2[10]={};
需要把str1拷贝到str2
int i;
//只需要把’\0’拷贝到str2数组中,所以其实不需要10次
for(i=0;i<10;i++){
str[2]=str1[i];
}
prinf("%s",str2) // hello
char str[3] ={}
char *p=strcpy(str3,str1)//实现字符拷贝,并且有返回值,指向str3的地址
把src字符串按字符拷贝到dest中 并且返回dest字符串
char *strcpy(char * dest,const char *src)
char *strncpy(char * dest,const char *src,size_t n)
char *mystrcpy(char *dest,const char *src){
assert(dest != null&&src!=null);//要先确定这个指针不是空指针,能用,即排除野指针
int i;
for(i=0;src[i]!=’\0’;i++){
dest[i]=src[i]
}
dest[i]=’\0’; //确保dest字符串结束
return dest;
}
字符串拷贝
#include <stdio.h>
#include <assert.h>
//dest 不是const src是const原因
//把*src拷贝到*dest中,*dest需要修改 不能有const *src不需要修改
char *mystrcpy(char *dest,const char *src){
assert(dest!=NULL&&src!=NULL);
int i;
for(i=0;src[i]!='\0';i++){
dest[i] = src[i];
}
dest[i] = '\0';//确保dest字符串结束
return dest;
}
char *mystrcpy1(char *dest,const char *src){
assert(dest!=NULL&&src!=NULL);//排除野指针
char *pdest = dest;//新建一个指针变量来保存初始的dest指针地址,方便主函数中调用,因为数组指针在函数中改变就是改变了。
while(*src!='\0'){
*dest = *src;
++dest;//通过改变地址位来达到逐个拷贝
++src;
}
*dest = '\0';
return pdest;
}
char *mystrcpy2(char *dest,const char *src){
assert(dest!=NULL&&src!=NULL);
char *pdest = dest;
while(*src!='\0'){
*dest++ = *src++;//*dest=*src;dest++;src++;
}
*dest = '\0';
return pdest;
}
char *mystrcpy3(char *dest,const char *src){
assert(dest!=NULL&&src!=NULL);
char *pdest = dest;
while((*dest = *src)!='\0'){// *dest = *src 当*src == '\0'时 *dest== '\0',因为条件是!='\0',所以会停止循环,但是'\0'已经赋给了dest,所以最后不用确保字符串结束。
++dest;
++src;
}
return pdest;
}
char *mystrcpy4(char *dest,const char *src){
assert(dest!=NULL&&src!=NULL);
char *pdest = dest;
while((*dest++ = *src++) != '\0');
return pdest;
}
int main(){
char str1[] = "Hello world,Hello java!";
char str2[100] = {};
char *p = mystrcpy(str2,str1);//这就是函数为啥要return pdest。返回的首地址,在打印整个字符串时会用到。
printf("%s\n",str2);
printf("%s\n",p);//因为传了dest的首地址,字符串是连串,所以一个首地址就能打印出整个字符串。
char str3[100] = {};
p = mystrcpy4(str3,str1);
printf("%s\n",p);
printf("%s\n",str3);
return 0;
}
可选性字符串拷贝
#include <stdio.h>
#include <string.h>
#include <assert.h>
char *mystrncpy(char *dest,const char *src,size_t n){
assert(dest!=NULL&&src!=NULL);
int i = 0;
for(i=0;i<n&&src[i]!='\0';i++){
dest[i] = src[i];
}
//for(;i<n;i++){//src已经拷贝完了 一直拷贝'\0'
if(i<n){
dest[i] = '\0';
}
return dest;
}
char *mystrncpy1(char *dest,const char *src,size_t n){
assert(dest!=NULL&&src!=NULL);
char *pdest = dest;
while(n>0&&*src!='\0'){
--n;
*dest++ = *src++;
}
if(n>0)
*dest = '\0';
return pdest;
}
char *mystrncpy2(char *dest,const char *src,size_t n){
assert(dest!=NULL&&src!=NULL);
char *pdest = dest;
while(n>0 && (*dest++ = *src++)!='\0'){
--n;
}
return pdest;
}
char *mystrncpy3(char *dest,const char *src,size_t n){
assert(dest!=NULL&&src!=NULL);
char *pdest = dest;
while(n-- > 0 && (*dest++ = *src++)!='\0');
return pdest;
}
int main(){
char str1[] = "Hello java,Hello c";
char str2[100] = {};
//需要从str1中拷贝10个字节到str2中
mystrncpy3(str2,str1,10);
printf("%s\n",str2);
char str3[100] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
mystrncpy3(str3,str1,10);//替换了前十个,即把Hello java拷贝到str3前10个
printf("%s\n",str3);
mystrncpy3(str2,str1,80);//如果拷贝个80,str1没有80个,则有多少拷多少,不会报错,即打印Hello java,Hello c
printf("%s\n",str2);
return 0;
}
字符追加
#include <stdio.h>
#include <string.h>
#include <assert.h>
char *mystrcat1(char *dest,const char *src){
assert(dest!=NULL && src!=NULL);//dest!=NULL && src!=NULL
//需要把字符串src接到dest后面
int len = 0;
while(dest[len]!='\0'){
++len;
}
int i;
for(i=0;src[i]!='\0';i++){
dest[len+i] = src[i];
}
dest[len+i] = '\0';
return dest;
}
char *mystrcat2(char *dest,const char *src){
assert(dest!=NULL && src!=NULL);
int len = 0;
while(dest[len]!='\0'){
++len;
}
int i=0;
while((dest[len+i] = src[i++])!='\0');
return dest;
}
char *mystrcat3(char *dest,const char *src){
assert(dest!=NULL && src!=NULL);
char *pdest = dest;
while(*dest != '\0'){
++dest;
}//先遍历到最后一个地址
while( (*dest = *src)!='\0'){
++dest;
++src;
}
return pdest;
}
char *mystrcat4(char *dest,const char *src){
assert(dest!=NULL && src!=NULL);
char *pdest = dest;
while(*dest != '\0'){
++dest;
}
while((*dest++ = *src++) !='\0');
return pdest;
}
char *mystrcat5(char *dest,const char *src){
assert(dest!=NULL && src!=NULL);
char *pdest = dest;
while(*dest++ != '\0');//没有循环体 *dest == '\0' 结束循环 后++ dest = dest+1
--dest;//为什么 后 判断之后再执行dest = dest+1
while((*dest++ = *src++) != '\0');
return pdest;
}
int main(){
char str1[100] = "Hello";
char str2[100] = " world";
mystrcat5(str1,str2);
printf("%s\n",str1);
char str3[100] = ",Hello C";
mystrcat5(str1,str3);
printf("%s\n",str1);
//mystrcat1(str1,NULL);
//mystrcat1(NULL,str1);
//mystrcat1(NULL,NULL);
return 0;
}
字符串可选性追加
#include <stdio.h>
#include <string.h>
#include <assert.h>
char *mystrncat1(char *dest,const char *src,size_t n){
assert(dest!=NULL && src!=NULL);
int len = 0;
while(dest[len]!='\0'){
++len;
}
int i;
for(i=0;i<n&&src[i]!='\0';i++){
dest[len+i] = src[i];
}
dest[len+i] = '\0';
return dest;
}
char *mystrncat2(char *dest,const char *src,size_t n){
assert(dest!=NULL && src!=NULL);
char *pdest = dest;
while(*dest != '\0'){
++dest;
}
while(n>0 && (*dest++ = *src++)!='\0'){//'\0'
--n;
}
*dest = '\0';//确保有'\0'
return pdest;
}
char *mystrncat3(char *dest,const char *src,size_t n){
assert(dest!=NULL && src!=NULL);
char *pdest = dest;
while(*dest != '\0'){
++dest;
}
while(n-->0 && (*dest++ = *src++)!='\0');
*dest = '\0';
return pdest;
}
int main(){
char str1[100] = "Hello";
char str2[100] = "world Hello c";
mystrncat3(str1,str2,30);
printf("%s\n",str1);
mystrncat3(str1,str2,5);
printf("%s\n",str1);
char str4[100] = {'a','b','\0','x','x','x','x','x','x'};
char str5[10] = "HHHH";
mystrncat3(str4,str5,2);
printf("%s\n",str4);
return 0;
}
字符串比较
是一个一个比较,比较ascii码大小
#include <stdio.h>
#include <string.h>
#include <assert.h>
//返回负数表示s1<s2 0 s1==s2 正数 s1>s2
int mystrcmp1(const char *s1,const char *s2){
assert(s1!=NULL&&s2!=NULL);
while(*s1!='\0' && *s2!='\0' && *s1 == *s2){//两个字符串都没结束 而且在当前位置的字符一样
++s1;
++s2;
}
return *s1-*s2;//返回负数就是s2大,正数就是s1大,0就是一样大,不一样的字符处想着ASCII码值相减
/*
if(*s1 > *s2){
return 1;
}else if(*s1 < *s2){
return -1;
}
return 0;
*/
}
int mystrcmp2(const char *s1,const char *s2){
assert(s1!=NULL&&s2!=NULL);
//while(*s1!='\0'&&*s2!='\0'&&*s1++==*s2++);//Error
for(;*s1!='\0'&&*s2!='\0'&&*s1==*s2; ++s1,++s2);
return *s1-*s2;
}
int main(){
char *s1 = "Hello";
char *s2 = "Hi";
//-1 s1小于s2 0 s1==s2 1 s1>s2
int res = mystrcmp2(s1,s2);
printf("%d\n",res);
res = mystrcmp2(s1,s1);
printf("%d\n",res);
res = mystrcmp2(s2,s1);
printf("%d\n",res);
//Hello world
//Hello java
//ascii 'w' > 'j'
res = mystrcmp2("Hello world","Hello java");
printf("%d\n",res);//
//Hello '\0' 0
//Hello java ' ' 正数
res = mystrcmp2("Hello","Hello java");
printf("%d\n",res);
res = mystrcmp2("He","Hi");//不是0
printf("%d\n",res);
return 0;
}
字符串可选性比较
#include <stdio.h>
#include <string.h>
#include <assert.h>
int mystrncmp1(const char *s1,const char *s2,size_t n){
assert(s1!=NULL && s2!=NULL);
while(n>0 && *s1!='\0' && *s2!='\0' && *s1==*s2){
--n;
++s1;
++s2;
}
if(n==0){//n个字节全部比较完了都相等
//return 0;一样的效果
--s1;
--s2;
}
return *s1-*s2;
}
int mystrncmp2(const char *s1,const char *s2,size_t n){
assert(s1!=NULL && s2!=NULL&&n!=0);
//如果前面n-1个字符都相等 最后一个字符不需要比较循环
while(--n>0 && *s1!='\0' && *s2!='\0' && *s1==*s2){
++s1;
++s2;
}
return *s1-*s2;//0 负 正
}
int main(){
int res = mystrncmp2("Hello","Hello java",5);
printf("%d\n",res);//0
res = mystrncmp2("Hello","Hello java",10);//-1
printf("%d\n",res);
res = mystrncmp2("Hello","Hellx a",5);
printf("%d\n",res);
return 0;
}