栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C指针学习

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C指针学习

第九课:指针(C语言的灵魂)
  1. 指针的定义:指针本质就是一个变量,而这个变量永远只能存储一个内存地址(编号)
    所以此变量对应的专业术语叫指针变量
    通过指针变量保存的地址就可以对这块内存区域任意访问(读查看,写修改)
    而指针指向的内存区域可以保存一个数字,而这个数字有数据类型

  2. 指针变量定义的语法格式:

    a)书写形式1:
    int * 变量名;
    例如:int * pa; //定义一个指针变量pa

    b)书写形式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)切记:定义指针变量后如果不初始化,此指针变量保存的一个地址值是随机的

也就是此指针变量指向任意内存区域,相当危险,因为此块内存区域
不是操作系统合法给你分配的内存,此指针变量为野指针!

  1. 指针变量初始化通过取地址&来进行:
    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用的!

问:
一旦通过指针变量来获取到指向的内存区域的首地址,如何通过指针变量
对指向的内存区域为所谓欲呢,也就是对指向的内存区域进行读查看或者写修改呢?
答:通过解引用运算符:*

  1. 解引用运算符(又称取目标运算符):*
    功能:就是通过指针变量对指向的内存区域进行读查看或者写修改
    语法格式:*指针变量 = 取目标
    例如:
    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
#include

int 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;

}
  1. 特殊指针:
    空指针和野指针

    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地址写入数据250

    c)切记:
    实际开发代码的编程规范(公式)
    如果定义一个指针变量,一开始不清楚它到底指向谁,千万不能不初始化,否则变成了野指针
    所以此时要求初始化为空指针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;
}
  1. 指针运算(核心并且坑爹)
    a)指针可以和一个整数做加减法运算,简称地址运算
    切记:计算结果和指针指向的变量数据类型有关系

    b)指针计算公式:

    1. char/unsigned char型指针+1,表示实际地址+1

    例如:
    char *pa = 0x1000; //假设pa指向0x1000
    pa++; //pa=0x1001

    1. short/unsgined short型指针+1,表示实际地址+2
      short *pa = 0x1000; //假设pa指向0x1000
      pa++; //pa=0x1002
    2. int/unsigned int /long/unsigned long型指针+1,表示实际地址+4
      long *pa = 0x1000; //假设pa指向0x1000
      pa++; //pa=0x1004

参考代码:pointer2.c

#include
int 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;
}
  1. 指针和数组的那点事儿(之前都是研究指针和变量的那点事儿)
    a)回顾数组相关内容
    定义数组:int a[4] = {‘A’, ‘B’, ‘C’, ‘D’};
    内存分布图:参见指针和数组.png

    结论:

    1. 数组名就是数组的首地址,
      也等于第一个元素的首地址,即: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(数据类型)得到时间地址相差的值

    2. &a[2]就是第2个元素的首地址

    3. a+2也是第2个元素的首地址

    4. *&a[2] 获取第2个元素的值

    5. *(a +2)同样表示获取第2个元素的值

    6. &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

#include
int 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 
  1. 指针和函数的那点事儿(目前掌握了指针和变量,指针和数组的关系)
    指针作为函数的形参(形参是指针变量)
    结果:
    函数通过形参指针变量可以对指向的内存为所欲为(任意读取其值或者修改其值)
    参考代码:swap.c
    #include
    
    void 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

#include

int 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:拷贝

回顾:

  1. C语言变量的四种类型
    局部非静态变量
    使用范围:定义到最近花括号
    内存生命周期:定义到最近花括号

    局部静态变量
    使用范围:定义到最近花括号
    内存生命周期:定义到最近花括号


    全局非静态变量
    使用范围
    定义到后续函数:同一个文件
    声明到后续函数:不同文件
    内存生命周期:程序启动到程序结束


    全局静态变量
    使用范围:仅限于当前文件到后续函数
    内存生命周期:程序启动到程序结束

  2. static关键字的特点
    修饰的变量和函数仅限于当前文件,在某些场合能够降低乱序概率

  3. 指针(C的灵魂)

    1. 指针变量概念:本质就是一个变量,只能存地址(32位,4字节/64位,8字节)
      俗称指向一块内存区域
      利用指针变量可以对指向的内存进行读查看,写修改

    2. 指针变量定义语法格式:
      int * p或者int* p或者int *p;
      int *p1, *p2, …, *pn;

    3. 指针变量的初始化
      int a = 250;
      int *p = &a;
      立刻浮现一个内存的分布图!
      或者
      p = &a; //p指向a
      int b = 520;
      p = &b; //p由指向a变成执行b

    4. 通过指针变量来访问指向的内存区域:&和*
      *p; //读查看
      *p = 新值; //写修改

    5. 两个特殊指针:空指针NULL和野指针
      空指针保存的地址是0
      野指针保存的地址是随机地址,不要有野指针,如果有一定要初始化为空指针
      空指针和野指针的编程技巧
      int *p; //野指针
      //让野指针变空指针
      int *p = NULL; //安全

      //一定注意:每次使用指针时,要判断指针是否为空指针
      if(NULL == p)
      不合法的地址

      else
      合法的地址

      或者
      if§
      合法的地址

      else
      不合法的地址

      或者
      if(!p)
      不合法的地址

      else
      合法的地址


    6. 指针的计算
      前提是指针的计算基于数据类型
      char *p = NULL; p++;
      short *p = NULL; p++;
      int *p = NULL; p++;

    7. 指针和数组的那点事儿(之前的知识都是指针和变量的关系)
      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; //实际地址空间差:3
      4=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++
      

  1. 常量,常量指针,指针常量,常量指针常量:围绕关键const(笔试题必考)
    a)常量定义:
    不可修改的值,例如:250,'A’等
    b)const关键字功能:常量化,四种形式:

    1. const可以修饰普通变量,一旦修饰该变量就会被当成常量处理
      一句话: 即其值一经初始化再也不能改
      例如:
      const int a = 250;
      a = 200; //gcc编译时会报错
      参考代码:const.c

      #include
      int main(void)
      {
      //形式1:普通变量常量化
      const int a =250;
      printf("a = %dn",a);   //打印查看
      //a = 200;  //gcc报错
      }      ```
      

    2. 常量指针 (最常用):
      不能通过指针变量来修改指向的内存区域的值(保护内存区域不可乱改),但是指针变量保存的地址是可以修改
      例如:
      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); //可以,仅仅是读查看
      用途:
      主要保护指向的内存区域的值不被非法的,无意的篡改

      #include
      int 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;
      }
      
    3. 常量指针常量 :
      指针变量本身不可修改并且指向的内容也不可修改,只能通过p来查看内存区域的值
      const int * const p;
      例如:

      int a = 100;
      const int* const p = &a;
      *p = 300; //不可以,可以修改指向的内存区域
      int b = 200;
      p = &b; //不可以,gcc报错
      printf("%dn", &p); //可以
      
      #include
      int 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;
      }
      
      1. 无类型指针void *加减几,实际的地址就是加减几
        void *p = 0x1000; //假设p保存0x1000地址
        p++; //p=0x1001
        p++; //p=0x1002

      2. 指针综合演练(高级进阶)
        例如:
        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

        #include
        int 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 *实现


  1. C语言字符串相关内容

    1. 回顾字符常量:
      用单引号包含,例如:‘A’,‘B’,'1’等
      实际内存存储的是对应的整型ASCII码
      占位符:%c

    2. 字符串定义:
      由一组连续的字符组成,并且用""包含起来,并且最后一个字符必须是’’ 此’’表示字符串的结束,此’’的ASCII码是0
      ‘0’的ASCII为48
      注意:研究字符串最终研究的就是里面的每个字符
      例如:“abcefg”(由字符’a’,‘b’,‘c’,‘d’m’’一个挨着一个组成)
      一般简写成:“abcd”(简化版,心里清楚,后面还有一个’’)

    3. 字符串特点
      a) 字符串的占位符:%s
      printf("%sn", “abc”); //直接跟字符串
      或者
      printf("%sn", 字符串的首地址);

      b) 字符串占用的内存空间是连续的,并且每个字节存储一个字符
      参见字符串内存图.png
      注意:’’字符串的结束符如果后面还有内容,那么这些内容为无效的
      例如:“abcefg”
      printf("%sn", “abcefg”); //abc

      c) 多个并列的字符串将来会由gcc帮你合并成一个字符串
      “abc”“efg"合并成"abcefg”
      printf(“abc”“efgn”); //abcefg
      参考代码str.c

    #include
    int main(void)
    {
    //1. 打印字符串
    printf("%sn","abcd");   //打印abcd
    printf("%sn","abcd");     //打印abcd
    printf("%sn","hjklabcd"); //hjkl
    
    //字符串合并
    printf("abc""efgn");  //等价于printf("abcdefn");
    return 0;
    }
    
    1. 字符串和指针的那点事儿
      a) 定义一个字符指针变量并且指向一个字符串,本质是指向这个字符串的首地址
      也就是指向字符串中第0个字符的首地址(也就是字符a的首地址)
      定义并且初始化字符串指针变量形式:
      char *p = “abcefg”; //p指向字符串"abcd"的首地址
      通过字符串指针变了打印字符串:printf("%sn",p); //abcd

      b) 如果让一个字符指针变量指向一个字符串,此字符串无需跟’’,gcc将来帮你添加’’
      完整版:char *p = “abcd”;
      简化版:char *p = “abcd”; //将来gcc编译自动会给字符串添加’’,心里要清楚内存要用5字节

      d)切记(必考):
      不能通过字符指针变量来修改字符串的每个字符,只能查看(笔试题必考)
      因为将来gcc编译器自动将字符串单独放到一个所谓的常量区中(一块特殊的内存,只能看)
      例如:
      char *p =“abcd”;
      printf("%sn",p); //查看
      *(p+2) = ‘C’ ; //目标将其中的’c’变 ‘C’,不行报错
      参考代码

      #include
      int main(void)
      {
      //1. 打印字符串
      printf("%sn","abcd");   //打印abcd
      printf("%sn","abcd");     //打印abcd
      printf("%sn","hjklabcd"); //hjkl
      
      //字符串合并
      printf("abc""efgn");  //等价于printf("abcdefn");
      
      //3.指针形式字符串
      char *p ="abcdefg111";
      printf("%sn",p);   //打印字符串
      *(p+2) = 'C';      //修改其中的字符,程序崩溃
      return 0;
      
      }
      

    2. 字符串和数组的那点事儿,两种写法:
      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’;

    3. 笔试题必考题目:
      编写一个字符串比较函数my_strcmp
      参考代码:strcmp.c

      #include
      int 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;
      }
      

  2. 标准C语言提供的字符串操作函数(都是大神写好的,咱直接调用即可)
    坚定信念:
    自己实现一个字符串操作函数完全没问题!
    如果要用以下大神写好的字符串操作函数,需要添加头文件:#include

    a) 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:架构


  1. 指针数组(实际开发很常用)
    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

    #include
    int 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;
    }
    

  2. 参数宏(又称宏函数)
    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) #(转字符串指令)和##(粘贴指令)

  1. #作用:将后面跟的宏参数转换为字符串
    例如:#N替换成了"N"
    参考代码:define.c

  2. ##作用:将其后面的宏参数进行替换然后与前面的部分粘连在一起,最终作为宏值替换
    例如: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

    #include
    printf("代码的这里错误:%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
    #include
    int 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

    #include
    int 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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/352497.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号