1
#include <stdio.h>
int main(int argc, const char *argv[])
{
char *p = NULL;
char *tmp = "12345678";
p = (char *)(tmp + 4);
//p[-1] = ? p[-5] = ?
return 0;
}
p指向字符5,p[-1]指向字符4,ascii码为52
p[-2]指向字符3,p[-3]指向字符2,p[-4]指向字符1
p[-5]指向整个字符串的前一个位置,为空,ascii值为0
2
#include<stdio.h>
#include<stdlib.h>
int main(int argc, const char *argv[])
{
char *data="12345678";
short *tmp=NULL;
char p[6]={0};
tmp=(short *)&p[2];
*tmp=atoi(&data[4]);
for(int i=0;i<6;i++){
printf("%x ",p[i]);
}
putchar(10);
return 0;
}
result:0 0 2e 16 0 0
tmp=(short *)&p[2]; //tmp指向p[2]的地址
*tmp=atoi(&data[4]) //p[2]=5678,二进制为0001 0110 0010 1110
以小端序存入 p[2]存入0010 1110 剩下一个字节存入p[3] 0001 0110
所以p[2]=2e p[3]=16
3 实现字符串内单词的反转
#include<stdio.h>
int main(int argc, const char *argv[]){
char s[]="hello world nihao beijing";
char t[100]={0};
char *p[100]; //定义一个指针数组
int i=1,j=0,k=0;
int a=0,b=0;
p[a++]=s;
while(s[i]){
if(s[i]==' '){
p[a++]=&s[i+1];
}
i++;
}
for(j=a-1;j>=0;j--){
k=0;
while(*(p[j]+k)!=' ' && (*(p[j]+k))){
t[b++]=*(p[j]+k);
k++;
}
t[b++]=' ';
}
t[b]='\0';
puts(s);
puts(t);
return 0;
}
4 a[N],1到N-1个整数,有一个重复的整数,设计一个时间复杂度为O(N)的函数找出这个整数。
#include<stdio.h>
int main(int argc, const char *argv[])
{
int a[]={1,3,6,4,2,4,5};
int t;
while(a[0]!=a[a[0]]){
t=a[0];
a[0]=a[t];
a[t]=t;
}
printf("出现次数最多的数为:%d ",a[0]);
putchar(10);
return 0;
}
1 malloc与free函数
#include <stdlib.h> 都包含在stdlib.h头文件下
void *malloc(size_t size);
功能:在堆区动态开辟一段内存空间
参数:size:申请空间的字节个数
返回值:
成功:返回申请到空间的首地址,此处void*为万能指针类型
失败:返回NULL
void free(void *ptr);
功能:释放一段在堆区开辟的空间
参数:ptr:要释放的目标空间的首地址
返回值:无
注意:malloc和free一定要成对出现,否则可能会出现内存泄漏。
释放完空间之后,指针要置空,防止产生野指针,也称为指针悬挂。
2 把malloc’封装成函数时
bool InitFunc(int **ptr)//此处应使用一个二级指针来作为函数参数,若要使用一级指针,函数类型应为指针函数,表示用指针p在堆区开辟20个字节
{
*ptr = (int *)malloc(20);
if(NULL == *ptr) //判断指针p所指向的内容是否为空
{
printf("malloc faliure !\n");
return false; //内容为空直接直接返回false
}
return true;
}
int main(int argc, const char *argv[])
{
int *p = NULL;
InitFunc(&p); //调用InitFunc函数,参数应该为&p
for(int i = 0 ; i < 5;i++)
{
p[i] = i + 1;
}
for(int i = 0 ; i < 5;i++)
{
printf("p[%d] = %d\n",i,p[i]); //输出可直接写成p[i],或者写为*(p+1),不能写成*p+i
}
free(p);
p = NULL;
q = NULL;
return 0;
}
3 回调函数
是一个通过函数指针调用的函数。如果把函数指针传递给另一个函数,当这个函数指针被用来调用它所指向的函数时,这个函数就是回调函数。
typedef int(*T)(int,int); //声明T为函数指针类型
int Max(int x,int y){…} //定义两个函数,Max和Min
int Min(int x,int y){…}
void MaoPao(int *a,int length,T p){…} //定义MaoPao函数
int main(int argc, const char *argv[])
{
MaoPao(a,length,Min); //在MaoPao函数的参数中调用函数指针
}
4 define的其他用法
int min;
#define MAX(a,b) ({int max;if(a > b) max = a;else max = b;max;})
//最后一个语句就是整个宏的返回值
#define MIN(a,b) do{if(a < b) min = a;else min = b;}while(0)
//最后一句话不是返回值
#define PRINT_MSG(msg) do{printf("%s\n",msg);return -1;}while(0)
//注意:这里的return不是宏函数的返回值,而是替换到main函数中,作为main函数的返回值
#define ERR_INTERNET_DISCONNECTED -5
#define S(n) #n //#代表字符串化
#define STRING "helloworld"
#define NAME(a,b) a##b //##代表标识符拼接
printf("%s\n",S(10)); //结果为10,为字符串
printf("%d\n",ERR_INTERNET_DISCONNECTED); //-5
printf("%s\n",S(ERR_INTERNET_DISCONNECTED));//结果为ERR_INTERNET_DISCONNECTED
printf("%s\n",S(-5)); //结果为-5,字符串
printf("hello = %s\n",NAME(STRING,)); //结果为helloworld
printf("%s\n",NAME(STR,ING)); //结果为helloworld
5 存储类型
static:
1.限定作用域只能在本文件中使用(修饰的变量或者函数)
2.延长变量的生命周期n
const:修饰的变量是只读变量,const修饰的局部变量在栈上
const修饰的全局变量,在.ro段
extern:延长变量或者函数的作用域
扩展其他文件的作用域到本文件中
extern int num1 = 400; //如果初始化,会造成变量的重复定义
volatile:保证数据每次都是从内存中获取的最新值,而不从缓存中取值,防止编译器对代码进行优化。
使用场景:
1.在多线程中访问同一个变量
2.在使用c语言操作硬件地址的时候,这个地址就需要加volatile
3.在中断处理函数和进程间都访问到的变量,这个变量就适合加上volatile。
volatile int *p = (volatile int *)0xC001a000;
*p = 1;
#define p *((volatile int *)0xc001a000)
p = 1;
p = 2;
6 内存管理
栈区: 局部变量,正在被调用的函数,形参等都在栈区,
操作系统自动申请自动释放,释放的时候,系统不会将
栈区的值清0,不初始化的化,其都是一些随机值
堆区:使用malloc分配的内存都在堆区,其特点:空间大,需要手动申请,手动释放
静态区:
.bss:使用static修饰的未初始化的变量(全局和局部)和全局未初始化的变量
.data:使用static修饰的已初始化的变量(全局和局部)和全局已初始化的变量
.text:文本段(代码段)
.ro:只读数据段
char *p = "helloworld";
const int a; //全局变量
#include <stdio.h>
static int count; // .bss段
int num = 10; //.data段
int num2; //.bss段
const int a2; //.ro段
int main(int argc, const char *argv[])
{
int a; //栈区
int *p = (int *)malloc(sizeof(int) * 10);//p:栈区 malloc:堆区
const int a1; //栈区
static int count; //.bss段
static int count2 = 900; //.data
char *s = "nihao"; //s:栈区 “nihao”://.ro段
return 0;
}
7 数组指针与指针数组
数组指针:
数组指针,即是指向数组的指针,数组指针中存放的是数组的地址。数组指针也称行指针。
指针数组:
是一个由n个指针类型元素(地址)组成的数组,当一个数组中的元素为地址时,这个数组就可以认为是一个指针数组。
数组指针:
int(*p)[n]; //定义了指向含有n个元素的数组指针
int a[n]; //定义数组
p=a; //将一维数组首地址赋值给数组指针p
int(* p)[4]; //定义了指向含有4个元素的一维数组的指针
int a[3][4];
p=a; //将二维数组的首地址赋值给p,也可是a[0]或&a[0][0]
p++; //表示p跨过行a[0][ ],指向了行a[1][ ]
指针数组:
int *p[3]; //定义指针数组
int a[3][4]; //定义二维数组
for(i=0;i<3;i++)
p[i]=a[i]; //通过循环将a数组每行的首地址分别赋值给p里的元素
8 函数指针与指针函数
函数指针:即指向这个函数的指针,定义为“数据类型 (*fun)(参数列表)”
指针函数:即返回值为指针的函数,定义为“数据类型 *fun(参数列表)”
在c语言中,变量有地址,函数也有地址。把函数的地址赋给函数指针,再通过函数指针调用这个函数就可以了。
函数指针工程应用作用:
1 隐藏调用接口和调用形式(提供统一的调用方式)
2 间接体现多态,提高代码的扩展性
① 定义函数指针,如int (*p)(int,int)
② 定义函数,如int fun(int x,int y)
③ 把函数的地址赋给函数指针,即p=fun;或者p=&fun
④ 通过函数指针调用函数,p(a,b)或者(*p)(a,b)
当函数的返回值为指针类型时,应该尽量不要返回局部变量的指针,因为局部变量是定义在函数内部的,当这个函数调用结束了,局部变量的栈内存也被释放了,因此不能够正确得到返回值。实际上,在内存被释放后,这个指针的地址已经返回,该地址已经是无效的,此时,对这个指针的使用是很危险的。
9 数组和指针的区别
空间分配:数组静态分配、指针动态分配
动态分配:
能在需要新内存的时候得到内存,不需要内存时就显式释放这部分内存,这种在程序运行时获取新内存空间的过程称为动态分配。
静态分配:
当声明一个全局变量时,编译器给在整个程序中持续使用的变量分配内存空间,这种分配方式称为静态分配,因为变量分配到了内存的固定位置。
访问效率:数组的地址是连续的,数组效率高,指针更加灵活。
安全性:数组安全性高,指针容易造成野指针、内存泄漏等问题。
函数形参:当数组作为函数形参时,会退化成指针,传入指针数组时,数组名退化为二维指针
10 复杂指针声明
int *(*(*fp1)(int))[10];
fp1是一个**函数指针**,指向的函数形参为1个int型,返回值为指针,指针指向具有10个int*类型的数组。
右左法则:从变量名开始,从右开始看,碰到括号就调转方向。
一 多级指针
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 100;
int *p = &a; //一级指针保存普通变量的地址
int **q = &p; //二级指针保存一级指针的地址
int ***w = &q;//三级指针保存二级指针的地址
//printf("a = %d %d %d %d\n",a,*p,**q,***w); //打印a的值
//&&a不能这么做,因为&a的结果是一个常量,常量不能取地址
printf("p = %p %p %p %p\n",&a,p,*q,**w); //打印a的地址
printf("q = %p %p %p\n",&p,q,*w); //打印p的地址
printf("w = %p %p\n",&q,w); //打印q的地址
return 0;
}
二 const关键字
6.1 全局变量和局部变量
const修饰全局变量:该变量存放在.ro段上(常量区)
const修饰局部变量: 该变量存放在栈上
6.2 const修饰全局变量和局部变量
#include <stdio.h>
const int num = 100; //全局变量
int a; //未初始化的全局变量默认值为0
int main(int argc, const char *argv[])
{
//num = 900; 被const修饰的全局变量不可以通过变量名去修改,编译会报错
int *p = #
//(*p)++; //通过指针去修改全局变量会引发程序崩溃
printf("num = %d\n",num);
const int num2 = 200; //const修饰的局部变量存放在栈区
//num2 = 300; //不可以通过变量名修改
int *q = &num2;
*q = 500; //可以通过指针修改被const修饰的局部变量
printf("num2 = %d\n",num2);
int a = 200; //当全局变量和局部变量命名冲突的时候,根据就近原则,优先使用局部变量
printf("a = %d\n",a);
return 0;
}
6.3 const修饰指针变量和修饰指针变量的类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUHDxCcp-1677223480010)(D:\1aaaasuqian\day\img\image-20221205174148917.png)]
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 100,b = 80;
//const int *p = &a; //const修饰的是*p,表示p指向的地址的值是一个常量,p的值可以被修改
//int * const p = &a; //const修饰的是p,表示p的值是一个常量,但是p指向的地址的值可以被修改
//int const *p = &a; //同第六行代码
const int * const p = &a; //第一个const修饰的是*P,第二个const修饰的是p,都不能修改
printf("a = %d,p = %p\n",a,p);
//*p = 300;
//p = &b;
printf("a = %d,p = %p\n",a,p);
return 0;
}
三 register关键词
register修饰的变量会在寄存器中开辟空间,使得运行效率大大提高。
寄存器空间不一定能申请到,如果申请不到,就会自动变成auto修饰
#include <stdio.h>
int main(int argc, const char *argv[])
{
register int i,j;
//&i;被register关键词修饰的变量不能取地址
//&j;
for(int i = 0 ; i < 10000;i++)
{
for(j = 1;j < 10000;j++)
{
}
}
return 0;
}
static,extern
四 函数
8.1 函数的相关概念
函数就是将一对要执行的代码放在一个代码块中,然后给这个代码起个名字,只要使用这个名字,就相当于使用这段代码,所以说函数很好的解决了代码的重用性,函数的编写尽量保证函数的功能单一性。
1.通过函数名找到函数的入口地址(函数名就是地址) int main(int argc,char *argv[])
2.给形参分配空间
3.传参,传值(把实参的值传递给形参的值)(值传递,地址传递)
4.执行函数体
5.结果返回到调用的地方
6.释放空间(栈空间)
8.2 定义一个函数以及函数的调用
#include <stdio.h>
/*
*日期:
*作者:
*功能:
*参数:
*返回值:
* */
//第一步:写出函数名
//第二步:实现函数的功能
//第三步:实现函数功能的过程中,如果过有一些参数需要从外部获取,考虑传参(实现形参)
//第四步:根据具体的需要决定返回值的类型
int Func(int x,int y); //函数的声明
int Func(int x,int y); //函数的声明可以声明多次
//void Func2();
int main(int argc, const char *argv[])
{
printf("helloworld\n");
int a = 1,b = 2,c = 3;
int ret = printf("a = %d,b = %d,c = %d\n",a,b,c);
printf("ret = %d\n",ret);
ret = Func(a,b);
printf("ret = %d\n",ret);
printf("%d\n",Func(b,c));
return 0;
}
int Func(int x,int y) //函数的定义或者叫函数的实现
{
return x + y;
}
/*int Func(int x,int y) //函数的定义只能定义一次
{
return x + y;
}*/
8.3 函数中调用函数
#include <stdio.h>
void func1();
void func2();
void func3();
void func4();
int main(int argc, const char *argv[])
{
func1();
return 0;
}
void func1()
{
printf("this is func1!\n");
func2();
}
void func2()
{
printf("this is func2!\n");
func3();
}
void func3()
{
printf("this is func3!\n");
func4();
}
void func4()
{
printf("this is func4!\n");
//func1();
}
练习:自己实现strcmp函数的功能
int MyStrcmp(const char *s1, const char *s2);
#include <stdio.h>
int MyStrcmp(const char *s1,const char *s2)
{
const char *p = s1,*q = s2;
while(*p == *q)
{
if(*p == '\0')
{
return *p -*q;
}
p++;
q++;
}
return *p -*q;
}
int main(int argc, const char *argv[])
{
char ch1[] = "helloeorld";
char ch2[] = "hellow";
int ret = MyStrcmp(ch1,ch2);
printf("ret = %d\n",ret);
return 0;
}
8.4 函数的传参方式
函数的传参方式一般分为三种:
全局变量传参:
一般不用,因为全局变量本身就可以在函数的内部直接使用,不需要专门传参。
值传参:将实参的值传递给形参
地址传递:将实参的地址传递给形参
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4e12Ah4T-1677223480013)(D:\1aaaasuqian\day\img\image-20221205174228284.png)]
#include <stdio.h>
void Swap(int *x,int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int main(int argc, const char *argv[])
{
int a = 100,b = 200;
printf("a = %d,b = %d\n",a,b);
//Swap(a,b);
Swap(&a,&b);
printf("a = %d,b = %d\n",a,b);
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wepNue8-1677223480014)(D:\1aaaasuqian\day\img\image-20221205174244270.png)]
8.5 一维数组的传参
#include <stdio.h>
#include <string.h>
//void Func(int *p,int lenth)
//void Func(int p[],int lenth)
void Func(int p[100],int lenth)
{
for(int i = 0;i < lenth;i++)
{
printf("%d ",p[i]);
}
putchar(10);
}
void Func2(char *s)
{
char *tmp = s;
//printf("s = %s\n",s);
while(*tmp != '\0')
{
printf("%c",*tmp++);
}
putchar(10);
printf("sizeof(s) = %ld\n",sizeof(s));
printf("strlen(s) = %ld\n",strlen(s));
}
int main(int argc, const char *argv[])
{
int arr[] = {1,2,3,4,4,5,6,7};
//一维数组的传参:数组首元素地址 + 数组的长度
int len = sizeof(arr)/sizeof(arr[0]);
Func(arr,len);
char ch1[] = "helloworld";
Func2(ch1);
return 0;
}
作业1:编写函数实现strlen,strcpy,strcat的功能
size_t strlen(const char *s);
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
作业2:编写函数,实现冒泡排序
作业3:有一个字符串,找出重复次数最多的那个字符,比如"abccccdddee"中的字符'c'.
作业4:10q
8.6 二维数组的传参
#include <stdio.h>
//二维数组传参
//1.传数组指针
//2.传原型
//void func(int (*p)[4])
void func(int p[3][4])
{
int i,j;
for(i = 0 ; i < 3;i++)
{
for(j = 0;j < 4;j++)
{
printf("%-5d",p[i][j]);
}
putchar(10);
}
}
int main(int argc, const char *argv[])
{
int a[3][4] = {10,20,30,40,50,60,70,80,90,100,110,120};
func(a);
return 0;
}
8.7 指针数组传参
#include <stdio.h>
//指针数组传参:
//1.传二级指针
//2.传指针数组
//void func(char **p,int length)
void func(char *p[],int length)
{
int i;
for(int i = 0 ; i < length;i++)
{
printf("%s\n",p[i]);
}
}
int main(int argc, const char *argv[])
{
char *s[4];
char ch1[] = "helloworld";
char ch2[] = "nihao";
char ch3[] = "hello nanjing";
char ch4[] = "welcome to jsetc";
s[0] = ch1;
s[1] = ch2;
s[2] = ch3;
s[3] = ch4;
int len = sizeof(s)/sizeof(s[0]);
func(s,len);
return 0;
}
8.8 命令行参数
#include <stdio.h>
//argc:存储的是命令行参数的个数
//argv:表示命令行参数的内容
int main(int argc, const char *argv[])
{
if(argc < 3)
{
printf("error,命令行参数太少!\n");
return -1;
}
printf("argc = %d\n",argc);
for(int i = 0 ; i < argc;i++)
{
printf("%s\n",argv[i]);
}
return 0;
}
五 指针函数
指针函数:本质是一个函数,函数的返回值是一个地址
#include <stdio.h>
#include <string.h>
char *FindSub(const char *ch1,const char *ch2)
{
char *p = (char *)ch1,*q = (char *)ch2;
int Ch1Len = strlen(p);
int Ch2Len = strlen(q);
for(int i = 0 ; i < Ch1Len - Ch2Len + 1;i++)
{
if(strncmp(p,q,Ch2Len) == 0)
{
return p;
}
p++;
}
return NULL;
}
int main(int argc, const char *argv[])
{
char ch1[] = "helloworld";
char ch2[] = "oword";
char *p = FindSub(ch1,ch2);
if(NULL != p)
{
printf("p = %s\n",p);
}
else
{
printf("ch2 不是ch1的子串!\n");
}
return 0;
}```
六 malloc/free
#include <stdlib.h>
void *malloc(size_t size);
功能:在堆区动态开辟一段内存空间
参数:size:申请空间的字节个数
返回值:
成功:返回申请到空间的首地址,此处void*为万能指针类型
失败:返回NULL
void free(void *ptr);
功能:释放一段在堆区开辟的空间
参数:ptr:要释放的目标空间的首地址
返回值:无
注意:malloc和free一定要成对出现,否则可能会出现内存泄漏。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OjjmkWnS-1677223480014)(D:\1aaaasuqian\day\img\image-20221207174225789.png)]
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
//int a[5] ={1,2,3,4,5}; //在栈区开辟5个int,并且将其赋值为 1,2,3,4,5
int *p = (int *)malloc(20); //表示用指针p在堆区开辟20个字节
if(NULL == p)
{
printf("malloc faliure !\n");
return -1;
}
int *q = p;
for(int i = 0 ; i < 5;i++)
{
p[i] = i + 1;
//*q++ = i + 1;
}
for(int i = 0 ; i < 5;i++)
{
printf("p[%d] = %d\n",i,p[i]);
}
free(p); //释放p开辟的一段内存(此处参数为开辟的空间的首地址)
p = NULL; //释放完空间之后,指针要置空,防止产生野指针,也称为指针悬挂
q = NULL;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-es8ZdoEv-1677223480015)(D:\1aaaasuqian\day\img\image-20221207174202477.png)]
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
bool InitFunc(int **ptr)
{
*ptr = (int *)malloc(20); //表示用指针p在堆区开辟20个字节
if(NULL == *ptr)
{
printf("malloc faliure !\n");
return false;
}
return true;
}
int main(int argc, const char *argv[])
{
//int a[5] ={1,2,3,4,5}; //在栈区开辟5个int,并且将其赋值为 1,2,3,4,5
int *p = NULL;
InitFunc(p);
int *q = p;
printf("q = %p\n",q);
for(int i = 0 ; i < 5;i++)
{
p[i] = i + 1;
//*q++ = i + 1;
}
for(int i = 0 ; i < 5;i++)
{
printf("p[%d] = %d\n",i,p[i]);
}
free(p); //释放p开辟的一段内存(此处参数为开辟的空间的首地址)
p = NULL; //释放完空间之后,指针要置空,防止产生野指针,也成为指针悬挂
q = NULL;
return 0;
}