-
指针的定义:指针本质就是一个变量,而这个变量永远只能存储一个内存地址(编号)
所以此变量对应的专业术语叫指针变量
通过指针变量保存的地址就可以对这块内存区域任意访问(读查看,写修改)
而指针指向的内存区域可以保存一个数字,而这个数字有数据类型
-
指针变量定义的语法格式:
a)书写形式1:
int * 变量名;
例如:int * pa; //定义一个指针变量pab)书写形式2:
int* 变量名;
例如:int* pa; //定义一个指针变量c)书写形式3:
例如:int *pa; //定义一个指针变量语义:都是定义一个指针变量pa,将来这个变量pa能够保存一块内存区域的首地址
也就是指针变量pa本身也会分配内存,只是它对应的内存用来存储其他内存区域的首地址
此过程简称pa指向某块内存区域
并且指针变量保存的首地址对应的内存区域保存着一个int类型的数据
也就是数据类型不是给指针变量使用,而是给指针变量指向的内存使用的切记:
指针变量分配的内存空间跟计算机硬件相关:
32位系统,一个地址值为32位,4字节,所以对应的的指针变量内存大小永远4字节
64位系统,一个地址值为64位,8字节,所以对应的的指针变量内存大小永远8字节
所以指针变量没有数据类型一说例如:
char *pa; //定义字符类型的指针变量pa,pa对应的内存为4字节,而pa指向的内存的数据类型
//为char类型,将来它指向的内存只能保存一个char类型的数字,对应的内存也就是1字节short *pb; //定义字符类型的指针变量pb,pb对应的内存为4字节,而pb指向的内存的数据类型
//为short类型,将来它指向的内存只能保存一个short类型的数字,对应的内存也就是2字节问:指针变量占用多大的内存空间呢?
答:得看保存的地址有多大,这个跟计算机硬件相关:
32位系统,一个地址值32位,4字节
64位系统,一个地址值64位,8字节结论:
指针变量分配的内存空间为4字节或者8字节,所以指针变量本身没有数据类型
只是它指向的内存区域保存的数字有数据类型,所以int不是给指针变量用
而是给指针变量指向的内存区域保存的数字用的d)连续定义指针变量形式:
int *pa, *pb; //定义两个指针变量
int *pa, pb; //pa是指针变量,而pb就是一个普通的int类型变量
e)切记:定义指针变量后如果不初始化,此指针变量保存的一个地址值是随机的
也就是此指针变量指向任意内存区域,相当危险,因为此块内存区域
不是操作系统合法给你分配的内存,此指针变量为野指针!
- 指针变量初始化通过取地址&来进行:
int a = 250; //分配4字节内存空间,存储250数字,而这个数字类型为int类型
int *pa = &a; //定义一个指针变量,也就是分配一个4字节内存空间(前提是32位)
保存变量a对应的内存空间的首地址
俗称pa指向a
务必脑子中浮现内存的指向图!
char b = ‘A’; //分配1字节内存空间,存储字符A的ASCII码
int *pb = &b; //定义指针变量分配4字节内存空间,然后保存变量b的首地址 简称pb指向b
注意:指针变量pa和pb 指定的数据类型 int ,char 不是给他们用,而是给a 和 b用的!
问:
一旦通过指针变量来获取到指向的内存区域的首地址,如何通过指针变量
对指向的内存区域为所谓欲呢,也就是对指向的内存区域进行读查看或者写修改呢?
答:通过解引用运算符:*
- 解引用运算符(又称取目标运算符):*
功能:就是通过指针变量对指向的内存区域进行读查看或者写修改
语法格式:*指针变量 = 取目标
例如:
char a = 100;
char *pa = &a;
或者:
char a = 100;
char *pa = NULL;
pa = &a;
//打印pa指向a的内存数据
printf("%dn", *pa); //100
//修改pa指向a的内存数据
*pa = 10; //结果是变量a的内存由原来的100变成10
结论:sizeof(指针变量名) = 4(永远等于4字节)
参考代码: pointer.c
#includeint main(void) { //定义初始化字符变量,分配1字节内存 char a ='A'; //定义初始化指针变量pa,pa指向a char *pa = &a; printf("&a = %p,pa = %p,&pa= %pn",&a,pa,&pa); //打印变量a,指针变量pa和指针变量本身的地址 printf("sizeof(a) = %d,sizeof(pa)=%dn",sizeof(a),sizeof(pa)); //打印变量a 和指针变量占用内存大小 //通过pa获取a的值 printf("%hhd,%cn",*pa,*pa); //通过pa修改a的值 *pa = 'B'; printf("%hhd,%cn",*pa,*pa); printf("%hhd,%cn",a,a); return 0; }
-
特殊指针:
空指针和野指针a)空指针:
空指针变量保存一个空地址,用NULL表示,其实就是编号为0地址
空指针不可以随意访问,否则造成程序的崩溃!例如:
int *pa = NULL;
printf(“pa指向的0地址保存的数据为%#xn”, *pa);
*pa = 250; //向0地址写入数据250注意:
全局指针变量没有初始化同样gcc也赋值一个null空地址b)野指针:
没有初始化的指针变量(局部指针变量),它保存着一个随机地址,指向着一块无效的内存
因为这块内存区操作系统并没有给你分配,如果对野指针进行非法访问
也会造成程序的崩溃!
int *pa; //pa就是野指针
printf(“pa指向的0地址保存的数据为%#xn”, *pa);
*pa = 250; //向0地址写入数据250c)切记:
实际开发代码的编程规范(公式)
如果定义一个指针变量,一开始不清楚它到底指向谁,千万不能不初始化,否则变成了野指针
所以此时要求初始化为空指针NULL,一旦初始化为NULL,将来程序后面使用时
时时刻刻要记得对指针变量进行安全的判断,判断它是否为NULL,如果为NULL,让程序结束
或者函数返回,如果为有效地址,程序才能继续通过指针变量进行操作例如:
int *pa; //不建议这么写,非常危险
//安全做法:
int *pa = NULL; //赋值为空指针
// 假如这里忘记了给pa赋值,pa还是null,但是好的编码规矩是访问指针之前一定做安全判断;
if(NULL == pa) {
printf("pa指向空指针,不能继续访问.n");
return -1 或者exit(0); //函数返回或者程序退出
} else {
printf("pa指向一块有效内存,可以继续访问n");
printf("%dn", *pa);
*pa = 250;
}
//安全做法:
int *pa = NULL; //初始化为空指针
int a = 250;
pa = &a; //让pa指向a,pa保存变量a的首地址(有效地址)
if(NULL == pa) {
printf("pa指向空指针,不能继续访问.n");
return -1 或者exit(0); //函数返回或者程序退出
} else {
printf("pa指向一块有效内存,可以继续访问n");
printf("%dn", *pa);
*pa = 251;
}
参考代码:pointer.c
#include#include int main(void) { int *pa = NULL; //安全做法 int a =250; pa = &a; // pa指向a,保存a变量的首地址(有效地址) //一定要进行安全判断 if(NULL == pa){ printf("pa为空指针,不能访问n"); exit(0); //立刻结束程序 }else{ printf("pa为有效指针.n"); *pa = 520; printf("a = %dn",*pa); } //2. 无指向 int *pb = NULL ; //安全做法 //一定要进行安全判断 if(NULL == pb){ printf("pb为空指针,不能访问n"); exit(0); //立刻结束程序 }else{ printf("pb为有效指针.n"); *pb = 520; printf("a = %dn",*pb); } return 0; }
-
指针运算(核心并且坑爹)
a)指针可以和一个整数做加减法运算,简称地址运算
切记:计算结果和指针指向的变量数据类型有关系b)指针计算公式:
- char/unsigned char型指针+1,表示实际地址+1
例如:
char *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1001- short/unsgined short型指针+1,表示实际地址+2
short *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1002 - int/unsigned int /long/unsigned long型指针+1,表示实际地址+4
long *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1004
参考代码:pointer2.c
#includeint main(void) { char a = 'A'; char *pa = &a; printf("pa = %pn",pa); pa++; printf("pa = %pn",pa); unsigned short b = 250; unsigned short *pb = &b; printf("pb = %pn",pb); pb++; printf("pb = %pn",pb); long c = 520; long *pc = &c; printf("pc = %pn",pc); pc++; printf("pc = %pn",pc); return 0; }
-
指针和数组的那点事儿(之前都是研究指针和变量的那点事儿)
a)回顾数组相关内容
定义数组:int a[4] = {‘A’, ‘B’, ‘C’, ‘D’};
内存分布图:参见指针和数组.png结论:
-
数组名就是数组的首地址,
也等于第一个元素的首地址,即:a = &a[a] ,数组名本质也是一个指针,只是这个指针保存的地址是
固定不变,不像指针变量保存的地址可以修改例如:
int a[4] = {‘A’, ‘B’, ‘C’, ‘D’};
int c = 250; //数组名不可修改
a = &c //万万不行的,gcc报错,这个数组名a不可修改,固定的一个地址// 指针变量可以修改
int pa = NULL;
int a =250;
int b = 520;
pa = &a ; //pa 指向a
pa = &b; //改成pa指向b
&a[4] - &a[1] = 3; // 也就是任意两个元素地址相减得到相隔元素的个数,拿元素个数sizeof(数据类型)得到时间地址相差的值 -
&a[2]就是第2个元素的首地址
-
a+2也是第2个元素的首地址
-
*&a[2] 获取第2个元素的值
-
*(a +2)同样表示获取第2个元素的值
-
&a[2] - a = a + 2 - a = 2个元素,表示第2个元素和第0个元素之间差2个元素 ,实际的地址差8个字节=2个元素*int
-
指针和数组关系公式:
int a[5] = {1,2,3,4,5};
int *pa = a; //定义指针变量保存整个数组的首地址
此时此刻脑子立马浮现内存图!
第i个元素地址 = &a[i] = &pa[i] = a+i = pa+i
第i个元素值 = a[i] = pa[i] = *(a+i) = *(pa+i)
注意:
a++ ; //非法
pa++; //合法
*a++; //非法
pa++; //合法,先算pa,后算pa加1
切记:
求数组元素个数公式:
sizeof(a) /sizeof(a[0]) = 元素个数; //合法
sizeof(pa) /sizeof(pa[0]); //不合法,因为sizeof(pa) 永远等于4
参考代码pointer_array.c
#includeint main(void) { int a[] = {1,2,3,4,5}; int *pa = a; //pa指向a,保存数组的首地址 int len = sizeof(a)/sizeof(a[0]); //获取元素个数 //int len = sizeof(pa)/sizeof(pa[0]); //错误:sizeof(pa)/sizeof(pa[0]) = 4/4 = 1 //写法1: //打印元素值 for(int i =0;i
- 指针和函数的那点事儿(目前掌握了指针和变量,指针和数组的关系)
指针作为函数的形参(形参是指针变量)
结果:
函数通过形参指针变量可以对指向的内存为所欲为(任意读取其值或者修改其值)
参考代码:swap.c#includevoid swap(int *pa,int *pb) //pa = &a,pb =&b; { int c = *pa; *pa = *pb; *pb = c; } void clear_bit(int *pc,int bit){ *pc &= ~(1< b) 指针作为函数的返回值
结果:
就是函数返回一个指针,此类函数又称指针函数
指针函数返回的地址对应的变量注意它的类型,切记:
不能返回局部非静态变量地址否则将会返回一个野指针,其他三类变量的地址可以返回
指针函数声明和定义的格式:指针变量 = 返回值数据类型 *函数名(形参表){函数体语句}
参考代码:pointer_function.c#includeint g_a =520; //全局非静态变量 static int g_b =520; //全局静态变量 int *A(void){ int a = 250; //局部非静态变量 static int b = 250; //局部静态变量 //return &a; //返回局部非静态变量地址,不可以,将来会返回一个野指针 //return &b; //返回局部静态变量地址,可以 //return &g_a; //返回全局非静态变量的地址,可以 return &g_b; //返回全局静态变量的地址,可以 } int main(void) { int *p = NULL; p = A(); //调用A函数,然后用p来接收报存A函数返回的变量地址 printf("%dn",*p); printf("%pn",p); *p = 30000; //修改指向的内存值,如果对野指针进行非法访问,直接崩溃 printf("%dn",*p); return 0; }
英文:assignment of read-only variable ‘a’:说明a被const修饰了,不可修改
s: string:字符串
cmp=compare:比较
cpy=copy:拷贝回顾:
C语言变量的四种类型
局部非静态变量
使用范围:定义到最近花括号
内存生命周期:定义到最近花括号
局部静态变量
使用范围:定义到最近花括号
内存生命周期:定义到最近花括号
全局非静态变量
使用范围
定义到后续函数:同一个文件
声明到后续函数:不同文件
内存生命周期:程序启动到程序结束
全局静态变量
使用范围:仅限于当前文件到后续函数
内存生命周期:程序启动到程序结束
static关键字的特点
修饰的变量和函数仅限于当前文件,在某些场合能够降低乱序概率
指针(C的灵魂)
指针变量概念:本质就是一个变量,只能存地址(32位,4字节/64位,8字节)
俗称指向一块内存区域
利用指针变量可以对指向的内存进行读查看,写修改指针变量定义语法格式:
int * p或者int* p或者int *p;
int *p1, *p2, …, *pn;指针变量的初始化
int a = 250;
int *p = &a;
立刻浮现一个内存的分布图!
或者
p = &a; //p指向a
int b = 520;
p = &b; //p由指向a变成执行b通过指针变量来访问指向的内存区域:&和*
*p; //读查看
*p = 新值; //写修改
两个特殊指针:空指针NULL和野指针
空指针保存的地址是0
野指针保存的地址是随机地址,不要有野指针,如果有一定要初始化为空指针
空指针和野指针的编程技巧
int *p; //野指针
//让野指针变空指针
int *p = NULL; //安全
…
//一定注意:每次使用指针时,要判断指针是否为空指针
if(NULL == p)
不合法的地址
…
else
合法的地址
…
或者
if§
合法的地址
…
else
不合法的地址
…
或者
if(!p)
不合法的地址
…
else
合法的地址
…
指针的计算
前提是指针的计算基于数据类型
char *p = NULL; p++;
short *p = NULL; p++;
int *p = NULL; p++;
指针和数组的那点事儿(之前的知识都是指针和变量的关系)
int a[] = {1,2,3,4,5}; //a是数组的首地址
int *p = a; //p保存数组的首地址,p指向数组a
脑子立马浮现内存分布图
[]:两步运算:先求地址后取值,a[3]: a+3, *(a+3)
int *p1 = &a[1];
int p2 = &a[4];
int n = p2 - p1 = 3; //实际地址空间差:34=12字节
结论:指针相减,得到的是相差元素个数!
通过指针变量p来读查看和写修改数组的方式:for(int i = 0; i < len; i++) printf("%d %d %d %dn", a[i], *(a+i), p[i], *(p+i)); for(int i = 0; i < len; i++) { a[i] *= 10; p[i] *= 10; *(a+i) *= 10; *(p+i) *= 10; } for(p = a; p < a + len; p++) printf("%d %d %d %dn", a[i], *(a+i), p[i], *(p+i)); for(p = a; p < a + len; ) printf("%d %d %d %dn", *p++); //先算*p,后p++
常量,常量指针,指针常量,常量指针常量:围绕关键const(笔试题必考)
a)常量定义:
不可修改的值,例如:250,'A’等
b)const关键字功能:常量化,四种形式:
const可以修饰普通变量,一旦修饰该变量就会被当成常量处理
一句话: 即其值一经初始化再也不能改
例如:
const int a = 250;
a = 200; //gcc编译时会报错
参考代码:const.c#includeint main(void) { //形式1:普通变量常量化 const int a =250; printf("a = %dn",a); //打印查看 //a = 200; //gcc报错 } ```
常量指针 (最常用):
不能通过指针变量来修改指向的内存区域的值(保护内存区域不可乱改),但是指针变量保存的地址是可以修改
例如:
int a = 250;
const int *p = &a; //定义初始化一个常量指针
或者
int const *p = &a; //定义初始化一个常量指针
*p = 200; //gcc编译时会报错
printf("%dn", *p); //可以,仅仅是读查看
int b = 300;
p = &b; //可以,让p由原来指向a,保存a变量的首地址现在指向b变量保存b变量的首地址
*p = 400; //gcc编译时会报错
printf("%dn", *p); //可以,仅仅是读查看
用途:
主要保护指向的内存区域的值不被非法的,无意的篡改#includeint main(void){ //形式2 :常量指针 int b = 520; const int *p = &b ; //定义初始化常量指针 printf("%dn",*p); //可以 / #include int main(void){ //形式3:指针常量 int d = 100; int* const p1 = &d; //p只能保存变量d的地址 *p1 =200; //可以 printf("d = %dn",d); int e =300; int e =300; //p1 = &e; //不可以,gcc报错 return 0; } 常量指针常量 :
指针变量本身不可修改并且指向的内容也不可修改,只能通过p来查看内存区域的值
const int * const p;
例如:int a = 100; const int* const p = &a; *p = 300; //不可以,可以修改指向的内存区域 int b = 200; p = &b; //不可以,gcc报错 printf("%dn", &p); //可以 #includeint main(void) { //形式1:普通变量常量化 const int a =250; printf("a = %dn",a); //打印查看 //a = 200; //gcc报错 //形式2 :常量指针(最常用) int b = 520; const int *p = &b ; //定义初始化常量指针 printf("%dn",*p); //可以 / #include int main(void) { int a =100; void *p = &a; //p指向a // printf("a = %dn",*p) //通过p无法获知要读取几个字节数据,gcc迷茫了 //解决办法1 :间接 //int *p1 =p; //隐式转换,不建议 int *p1 = (int *)p; //强制转换,将无类型指针转换成int类型指针,p1也指向变量a *p1 = 200; printf("a = %dn",*p1); //解决办法2:直接 * (int *)p = 300; //直接将无类型指针进行强制转换然后解引用 printf("a = %dn",*(int *)p); return 0; }
无类型指针void *加减几,实际的地址就是加减几
void *p = 0x1000; //假设p保存0x1000地址
p++; //p=0x1001
p++; //p=0x1002
指针综合演练(高级进阶)
例如:
int a = 0x12345678; //连续分配4字节内存,并且4字节内存放置的数据是 0x12345678
void *p = &a; //虽然p指向a,但是通过p无法获取a变量的数据类型
*p = 300; //gcc编译报错,不清楚到底取几个自己的数据,没办法只能给你报错问:如何通过一个指针来获取a的4字节数据或者其中任意1字节数据或者其中任意2字节数据呢?
答:通过两种方法
方式1:通过有类型的指针变量来获取1字节,2字节,4字节
int a = 0x12345678;
//char *p = &a; //隐式转换,代码可读性不高,gcc还要给个警告
char *p = (char )&a; //强制转换,提高代码的可读性,将a变量的指针类型由int类型转换成char//获取其中1字节数据
printf("%#xn", *p++); //0x78
printf("%#xn", *p++); //0x56
printf("%#xn", *p++); //0x34
printf("%#xn", *p++); //0x12//获取2字节:
int a = 0x12345678;
//short *p = &a; //隐式转换,代码可读性不高 gcc还要给个警告
short *p = ( short )&a; //强制转换,将&a的int类型转换成short
printf("%#xn", *p++); //0x5678
printf("%#xn", *p++); //0x1234//获取4字节数据
int *p = &a;
printf("%#xn",*p); //0x12345678参考代码 pointer3.c
#includeint main(void) { int a = 0x12345678; //1. 通过指针获取其中1字节数据 char *p1 =(char *)&a; //将int类型指针强转为char类型指针,否则采用隐士转换,p1保存变量a的首地址 //打印 printf("%#xn",*p1++); printf("%#xn",*p1++); printf("%#xn",*p1++); printf("%#xn",*p1++); //2.通过指针获取其中2字节数据 short *p2 = (short *)&a; //将int类型指针强转为short类型指针,否则采用隐士转换,p2保存变量a的首地址 //打印 printf("%#xn",*p2++); printf("%#xn",*p2++); //3.通过指针获取其中4字节数据 int *p3 =&a; //打印 printf("%#xn",*p3); return 0; } 方式2:通过无类型指针变量来访问操作
例如:int a = 0x12345678;
void *p = &a; //无需转换,p指向a
//获取1字节数据
char *p1 = (char *)p; //将p强制类型转换成char 类型指针
printf("%#xn", *p1++); //0x78
printf("%#xn", *p1++); //0x56
printf("%#xn", *p1++); //0x34
printf("%#xn", *p1++); //0x12// 获取2字节数据
short *p2 = (short *)p; //将p强制类型转换成short类型指针
printf("%#xn", *p2++); //0x5678
printf("%#xn", *p2++); //0x1234或者直接转换使用
printf("%#xn",*(short *)(p+0)); //先算p+0 ,然后对结果进行强转为short *指针,最后解引用 星
printf("%#xn",(short *)(p+2));//获取4字节数据
int *p3 = (int *)p; //将p强制类型转换成int类型指针
printf("%#xn",n1);
或者直接操作无类型指针
printf("%#xn",(int *)(p+0)); //先算p+0 ,然后对结果进行强转为int *指针,最后解引用 *结论:
此方法虽然满足要求,但是如果做隐式转换,gcc老给一个警告
问:能否将警告去掉呢?还有能够去掉指针变量++时跟类型相关问题呢?
答:
通过无类型指针void *实现
英语:
C语言字符串相关内容
回顾字符常量:
用单引号包含,例如:‘A’,‘B’,'1’等
实际内存存储的是对应的整型ASCII码
占位符:%c
字符串定义:
由一组连续的字符组成,并且用""包含起来,并且最后一个字符必须是’ ’ 此’ ’表示字符串的结束,此’ ’的ASCII码是0
‘0’的ASCII为48
注意:研究字符串最终研究的就是里面的每个字符
例如:“abcefg ”(由字符’a’,‘b’,‘c’,‘d’m’ ’一个挨着一个组成)
一般简写成:“abcd”(简化版,心里清楚,后面还有一个’ ’)
字符串特点
a) 字符串的占位符:%s
printf("%sn", “abc ”); //直接跟字符串
或者
printf("%sn", 字符串的首地址);b) 字符串占用的内存空间是连续的,并且每个字节存储一个字符
参见字符串内存图.png
注意:’ ’字符串的结束符如果后面还有内容,那么这些内容为无效的
例如:“abc efg ”
printf("%sn", “abc efg”); //abcc) 多个并列的字符串将来会由gcc帮你合并成一个字符串
“abc”“efg"合并成"abcefg”
printf(“abc”“efgn”); //abcefg
参考代码str.c#includeint main(void) { //1. 打印字符串 printf("%sn","abcd "); //打印abcd printf("%sn","abcd"); //打印abcd printf("%sn","hjkl abcd"); //hjkl //字符串合并 printf("abc""efgn"); //等价于printf("abcdefn"); return 0; }
字符串和指针的那点事儿
a) 定义一个字符指针变量并且指向一个字符串,本质是指向这个字符串的首地址
也就是指向字符串中第0个字符的首地址(也就是字符a的首地址)
定义并且初始化字符串指针变量形式:
char *p = “abcefg”; //p指向字符串"abcd"的首地址
通过字符串指针变了打印字符串:printf("%sn",p); //abcdb) 如果让一个字符指针变量指向一个字符串,此字符串无需跟’ ’,gcc将来帮你添加’ ’
完整版:char *p = “abcd ”;
简化版:char *p = “abcd”; //将来gcc编译自动会给字符串添加’ ’,心里要清楚内存要用5字节d)切记(必考):
不能通过字符指针变量来修改字符串的每个字符,只能查看(笔试题必考)
因为将来gcc编译器自动将字符串单独放到一个所谓的常量区中(一块特殊的内存,只能看)
例如:
char *p =“abcd”;
printf("%sn",p); //查看
*(p+2) = ‘C’ ; //目标将其中的’c’变 ‘C’,不行报错
参考代码#includeint main(void) { //1. 打印字符串 printf("%sn","abcd "); //打印abcd printf("%sn","abcd"); //打印abcd printf("%sn","hjkl abcd"); //hjkl //字符串合并 printf("abc""efgn"); //等价于printf("abcdefn"); //3.指针形式字符串 char *p ="abcdefg111"; printf("%sn",p); //打印字符串 *(p+2) = 'C'; //修改其中的字符,程序崩溃 return 0; }
字符串和数组的那点事儿,两种写法:
a)写法1:
char a[] = {‘a’, ‘b’, ‘c’, ‘ ’};
注意:如果想把a当成字符串,需要手动最后添加’ ’,如果不添加a仅仅就是一个包含三个元素
的数组,而不是有效字符串
b)写法2:
char a[] = “abc”;
注意:无需添加’ ’,将来编译器自动追加’ ’,所以对于次数组将来实际分配4字节内存c)切记:
不管是哪种写法,字符数组中的元素都是可以修改的(笔试题必考)
例如:
将第2元素’c’修改为’C’:
a[2] = ‘C’;
或者
*(a + 2) = ‘C’;
笔试题必考题目:
编写一个字符串比较函数my_strcmp
参考代码:strcmp.c#includeint main(void) { //4.数组形式的字符串 char a[] = {'x','y','z',' '}; printf("%sn",a); //查看 a[2] = 'Z'; //修改 printf("%sn",a); char b[] = "mnab"; printf("%sn",b); b[2] ='A'; printf("%sn",b); printf("sizeof(b)=%dn",sizeof(b)); //5字节,gcc自动追加一个' ' return 0; }
标准C语言提供的字符串操作函数(都是大神写好的,咱直接调用即可)
坚定信念:
自己实现一个字符串操作函数完全没问题!
如果要用以下大神写好的字符串操作函数,需要添加头文件:#includea) strlen函数:
功能获取字符串有效长度(不包括’ ’)
例如:printf(“长度:%dn”, strlen(“abc”)); //3
或者
char *p = “abc”;
printf(“长度:%dn”, strlen§); //3
b) strcat函数:功能是字符串拼接
例如:
char a[10] = “abc”;
char *p = NULL;
p = strcat(a, “xyz”); //把xyz拼接在abc的后面保存到数组中
printf("%s %sn", p, a); //abcxyz abcxyz
c) strcmp函数:功能是字符比较函数
例如:
char *p1 = “abc”;
char *p2 = “xyz”;
int ret = strcmp(p1, p2);
int ret = strcmp(“abc”, “xyz”); //本质最终传递的是字符串的首地址
d) strcpy:字符串拷贝函数,会覆盖原先的字符串
例如:
char a[10] = “abc”;
char *p = NULL;
p = strcpy(a, “xyzmn”);
printf("%s %sn", p, a);
e) sprintf(游哥最爱)格式化输出函数:按照指定的个数获取字符串
例:功能把数字(250)转成字符串"250"保存到数组中
例如:
char a[50] = {0};
sprintf(a, “%d %g, %c”, 250, 250.2, ‘A’);
printf("%s", a);参考代码 string1.c
#include#include //为了声明大神的字符串操作函数 int main(void) { char *p1 = "abc"; printf("%d %dn",strlen("abcd"),strlen(p1)); //获取有效字符个数 //2. strcat 演示 char a[10] = "abc"; //一次性分配10字节,目前用了3个字节,其余都是0 char *p2 = NULL; p2 = strcat(a,"xyz"); //将"xyz"放到数组a中并且放到"abc"后面,并且返回数组的首地址,其实p2 = a printf("%s %sn",a,p2); //打印字符串 printf("%s %sn",a,p2); //打印字符串 printf("%d %dn",sizeof(a),strlen(a)); //10 6 //3. strcmp演示 int ret =0; ret =strcmp("abc","abd"); //"abc"小于 "abd" :ret =-1 printf("%dn",ret); ret = strcmp("abd","abc"); //"abd" 大于 "abc": ret =1 printf("%dn",ret); ret = strcmp("abc","abc"); //"abc" 等于 "abc" : ret =0 printf("%dn",ret); char *p3 ="abc"; char *p4 = "abd"; ret = strcmp(p3,p4); //p3
平方:square
file:文件
line:行号
function:函数
date:日期
time:时间
end:结束
architecture:架构
指针数组(实际开发很常用)
a) 指针数组概念:数组中每个元素都是一个指针(地址)
元素只能是地址,不能是普通的数据
指定数组定义语法格式:
数据类型 *数组名[元素个数] = {地址列表};
例如:int a =10 ,b =20,c=30; //以前做法:定义三个指针变量分别指向a,b,c int *pa = &a; int *pb = &b; int *pb = &c;如果定义大量的变量和对应的指针变量,代码极其啰嗦!
可以采用指针数组优化代码:例如:
int *p[3] = {&a,&b,&c} ; //无需定义大量指针变量
具体后续玩法跟数组一模一样:结果:
p[0] = &a = *(p+0)
p[1] = &b = *(p+1)
p[2] = &c = *(p+1)通过地址获取变量的值:
*p[0] = *&a = **(p+0) = 10
*p[0] = *&a = **(p+0) = 20
*p[0] = *&a = **(p+0) = 30元素个数 = sizeof§ /sizeof(p[0])
c) 应用场景:
如果将来需要定义大量的一堆堆的指针变量,反而让代码看起来极其繁琐
采用指针数组来进行统一,类似数组来替换大量的变量
现在用指针数组替换大量的指针变量
例如:
int a = 10, b = 20, c = 30, d = 40, e = 50; …
int *pa = &a, *pb = &b, *pc = &c, *pd = &d, *pe = &e; …
相当累啊,我好难啊,烦死了,想起指针数组来优化:
int *p = {&a, &b, &c, &d, &e …};
参考代码:array.c#includeint main(void) { int a =10,b = 20,c = 30; int *p[3] = {&a,&b,&c}; int len = sizeof(p)/sizeof(p[0]); //打印值 for(int i = 0;i int main(void) { //形式1: char *p[] = {"abc","efg"}; int len = sizeof(p)/sizeof(p[0]); //打印 for(int i = 0;i #define PI (3.14) int main(void) { double r =10; //半径 //优秀代码 printf("周长:%lfn",2*PI*r); printf("面积:%lfn",PI*r*r); //垃圾代码:如果将来需要精确改动圆周率,改动量很大 printf("周长:%lfn",2*3.14*r); printf("周长:%lfn",2*3.14*r); return 0; }
参数宏(又称宏函数)
a) 语法格式:#define 宏名(宏参数) (宏值)
注意:宏值里面的宏参数不要少圆括号
如果宏参数有多个,用逗号来分区
例如:#define SQUARE(x) ((x)*(x))
#define SUB(x, y) ((x) - (y))
语义:在预处理时,gcc先将宏参数替换成实际值,然后将代码中的宏名最终全部替换成宏值
注意:宏值里面的宏参数不要忘记圆括号()
优点:宏函数比普通函数的代码执行效率要高
宏函数将来在预处理是做了替换,程序运行时直接运行
宏函数涉及调用,传参(赋值)的过程,这个过程是需要消耗CPU资源案例:
宏函数演练,参考代码define.c
编译好习惯:gcc -E -o define.i define.c
vim define.i //好习惯看看替换的结果
gcc -o define define.i利用宏函数实现数的平方
#include#define SQUARE(x) ((x)*(x)) #define SUB(x,y) ((x)-(y)) #define CLEAR_BIT(data,n) (data &=~(1<
c) #(转字符串指令)和##(粘贴指令)
#作用:将后面跟的宏参数转换为字符串
例如:#N替换成了"N"
参考代码:define.c##作用:将其后面的宏参数进行替换然后与前面的部分粘连在一起,最终作为宏值替换
例如:id##N替换成 id1
参考代码:define.c#include#define PRINT(N) (printf(#N"=%dn",N)) int main(void) { int b = 10,c =20; PRINT(b); //printf("b""=%dn",b) 等价于printf("b= %dn",b); PRINT(c); return 0; }
d) 编译器已经定义好的宏(直接使用,无需#define),实际开发非常常用!主要用于调试,log日志记录
注意:都是两个下划线|宏 | 含义| 占位符|
| — | — | — |
|FILE | 表示当前文件名| %s
|LINE | 表示当前行号 | %d|
|FUNCTION| 表示当前所在的函数名 %s| 等价于__func__|
|DATE | 文件创建的日期 | %s|
|TIME| 文件创建的时间 | %s|实际开发日志使用公式:
printf(“代码的这里错误:%s, %s, %s, %s, %d,出现了一个野指针的访问错误.n”,
DATE, TIME, FILE, FUNCTION, LINE);
参考代码 define.c#includeprintf("代码的这里错误:%s, %s, %s, %s, %d,出现了一个野指针的访问错误.n", __DATE__, __TIME__, __FILE__, __FUNCTION__, __LINE__); return 0; }
e) 用户可以动态预定义宏 : 通过gcc的-D选项来指定宏
作用:程序在编译的时候将-D选项指定宏给程序传递一个值
注意:如果宏是一个字符串,那么-D后面的宏值需要用"说明
例如:
gcc -DSIZE=250 -DWELCOME=“大神来了”
结果代码中可以直接使用SIZE和WELCOME宏,并且他们的值分别是250和"达内"
代码使用: printf("%d %sn", SIZE, WELCOME);
参考代码 define1.c//编译命令:gcc -DSIZE=5 -DEND="很开心" -o define1 define1.c #includeint main(void) { int a[SIZE] = {0}; //赋值 for(int i= 0;i
f) 条件编译命令 (大型软件代码用的非常多)
条件编译:
符合条件的,代码就编译,不符合条件的,代码不编译,让代码删除消失#if //如果,例如:#if A==1
#ifdef //如果定义了…
#ifndef //如果没有定义 …
#elif //否则如果
#else //否则
#endif //和#if,#ifdef,#ifndef配对使用
#undef //取消定义,和#define死对头,例如:#define PI (3.14) #undef PI
参考代码:if.c#includeint main(void) { //编译命令:gcc -E -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if //编译命令:gcc -E -DA=1 -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if #if A==1 //如果A ==1,条件成立,printf代码将被编译,否则不编译(类型删除代码) printf("1.n"); #endif //跟#if配对 //#if ... #else 演示 //编译命令:gcc -E -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if //编译命令:gcc -E -DB=1 -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if #if B==1 //如果B等于1条件成立编译printf2 ,否则编译printf3 printf("2.n"); #else printf("3.n"); #endif //跟#if配对 //#ifdef /#dendef ...#else演示 //编译命令:gcc -E -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if //编译命令:gcc -E -DC -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if #ifndef C //#ifdef C //如果定义C宏,编译print4,否则编译printf5 printf("4.n"); #else //可以不加 printf("5.n"); #endif //跟#ifdef / #ifndef配对 //if defined ...#else 演示 //编译命令:gcc -E -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if //编译命令:gcc -E -DD -E -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if //编译命令:gcc -E -DE -E -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if //编译命令:gcc -E -DD -DE -o if.i if.c ; vim if.i 查看结果; gcc -o if if.i ./if #if defined(D) printf("6.n"); #elif !defined(DA) && !defined(E) printf("7.n"); #else printf("8.n"); #endif return 0; } 实际开发的演示代码:
利用条件编译让一套代码支持不同的CPU(X86,ARM,POWERPC,DSP,FPGA等),也不会增大代码的体积
否则要分别给每类cpu都写一套代码,非常繁琐!vim A(void)
{ #if ARCH==X86 只编译X86相关代码 #elif ARCH==ARM 只编译ARM相关代码 #elif ARCH ==POWERPC 只编译POWERPC代码 #elif ARCH ==DSP 只编译DSP代码 #else 只编译FPGA代码 #endif ... }编译生成 ARM的CPU代码:gcc -DARCH=ARM xxx xxx.c



