【嵌入式开发】C语言 内存分配 地址 指针 数组 参数 实例解析(二)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【嵌入式开发】C语言 内存分配 地址 指针 数组 参数 实例解析(二)

3. 指针与地址





(1) & 与 * 操作



取地址运算符 & : p = &c;


-- 表达式解析 : 将 c 的地址赋值给 变量 p, p 是指向 c 变量的指针;


-- & 可以使用的情况 : 取地址操作 只能用于内存中的对象, 如变量 或 数组, 栈内存 堆内存 都可以;


-- & 不适用的情况 : 不能用于 表达式, 常量, register类型变量;




间接引用运算符 : * ;


-- 声明指针 : int *p ; 该表达式的含义是 *p 的结果是 int 类型, 声明变量 a, int a, 声明指针 *p , int *p;


-- 获取指针指向的值 : int a = *p ;




(2) 指针定义解析



声明指针 和 函数 : int *p, max(int a, int b), 声明指针变量 语法 与声明 变量语法类似, 同理声明函数也一样;


-- 原理 : *p 和 max()返回值 类型都是 int 类型;




指针指向 : 每个指针都必须指向某种特定类型;


-- 例外 : void *p 可以指向任何类型, 但是 p 不能进行取值运算, *p 是错误的, 因为不知道 p 指向的数据类型;




(3) 指针运算及示例



指针相关运算 : int x = 0; int *p = &x; 那么*p 就可以代表x;


-- 算数运算 : x = x + 1; 等价于 *p = *p + 1 ; int y = x + 1; 等价于 int y = *p + 1;


-- 自增运算 : 前提 : ++, * 运算顺序是自右向左;  ++*p 和 (*p)++, p 指向的值自增1, 注意要加上括号, 否则会将地址自增;


-- 指针赋值 : int *p, *q; int a = 0; p = &a; q = p; 最终结果 p 和 q 都指向了 变量 a;




示例程序 :



/*************************************************************************
    > File Name: pointer_address.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: Mon 10 Mar 2014 09:52:01 PM CST
 ************************************************************************/
#include<stdio.h>
int main(int argc, char ** argv)
{
        int *p, *q;
        int a = 10, b;
        //p指针指向a变量
        p = &a;
        //*p 可以代替 a 进行运算
        ++*p;
        b = *p + 5;
        //指针之间可以直接相互赋值
        q = p;
        //打印 p 和 q 指针指向的值
        printf("*p = %d \n", *p);
        printf("*q = %d \n", *q);
        return 0;
}


执行结果 :


[root@ip28 pointer]# gcc pointer_address.c 
[root@ip28 pointer]# ./a.out 
*p = 11 
*q = 11



4. 函数参数的传值调用和传址调用



(1) 传值调用 和 传址调用



传值调用 : 以传值的方式将参数传递给函数, 不能直接修改主函数中变量的值, 仅仅是将副本传递给了函数;




传址调用 : 将 变量的指针 传递给函数, 当函数对指针进行操作的时候, 主函数中的值也进行了对应变化;




交换函数示例1 :


/*************************************************************************
    > File Name: swap.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: Mon 10 Mar 2014 11:07:18 PM CST
 ************************************************************************/
#include<stdio.h>
void swap_1(int a, int b)
{
        int temp;
        temp = a;
        a = b;
        b = temp;
        printf("swap_1 传值 函数 a = %d, b = %d \n", a, b);
}
void swap_2(int *a, int *b)
{
        int temp;
        temp = *a;
        *a = *b;
        *b = temp;
        printf("swap_2 传址 函数 a = %d, b = %d\n", *a, *b);
}
int main(int argc, char **argv)
{
        int a = 10, b = 5;
        printf("初始值 : a = %d, b = %d \n\n", a, b);
        swap_1(a, b);
        printf("执行 swap_1 函数, a = %d, b = %d \n\n", a, b);
        swap_2(&a, &b);
        printf("执行 swap_2 函数, a = %d, b = %d \n", a, b);
        return 0;
}


执行结果 :



[root@ip28 pointer]# gcc swap.c 
[root@ip28 pointer]# ./a.out 
初始值 : a = 10, b = 5 
swap_1 传值 函数 a = 5, b = 10 
执行 swap_1 函数, a = 10, b = 5 
swap_2 传址 函数 a = 5, b = 10
执行 swap_2 函数, a = 5, b = 10



示例解析 :


-- 传值调用 : swap_1 是传值调用, 传入的是 main 函数中的 a b 两个变量的副本, 因此函数执行完毕后, 主函数中的值是不变的;


-- 传址调用 : swap_2 是传址调用, 传入的是 a , b 两个变量的地址 &a, &b, 当在swap_2 中进行修改的时候, 主函数中的 a,b变量也会发生改变;






(2) 高级示例



需求分析 : 调用getint()函数, 将输入的数字字符 转为一个整形数据;




getch 和 ungetch 函数 :


-- 使用场景 : 当进行输入的时候, 不能确定是否已经输入足够的字符, 需要读取下一个字符, 进行判断, 如果多读取了一个字符, 就需要将这个字符退回去;


-- 使用效果 : getch() 和 ungetch() 分别是预读下一个字符, 和 将预读的字符退回去, 这样对于其它代码而言, 没有任何影响;




注意的问题 : 出现问题, 暂时编译不通过, 找个C语言大神解决;




代码 :



/*************************************************************************
    > File Name: getint.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: Mon 10 Mar 2014 11:40:19 PM CST
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define SIZE 5
int getint(int *p)
{
  //sign 是用来控制数字的正负
  int c, sign;
  //跳过空白字符, 如果是空白字符, 就会进行下一次循环, 直到不是空白字符为止
  while(isspace(c = getc(stdin)));
  //如果输入的字符不是数字, 就将预读的数据退回到标准输入流中
  if(!isdigit(c) && c != EOF && c != '+' && c != '-')
  {
  ungetc(c, stdin);
  return 0;
  }
  /*
  * 如果预读的是减号, 那么sign 标识就是 -1, 
  * 如果预读的是加号, 那么sign 标识就是 1;
  */
  sign = (c == '-') ? -1 : 1;
  //如果 c 是 加号 或者 减号, 再预读一个字符&
  if(c == '+' || c == '-')
  c = getc(stdin);
  for(*p = 0; isdigit(c); c = getc(stdin))
  *p = 10 * *p + (c - '0');
  *p *= sign;
  if(c != EOF)
  ungetc(c, stdin);
  return c;
}
int main(int argc, char **argv)
{
  int n, array[SIZE], i; 
  for(n = 0; n < SIZE && getint(&array[n]) != EOF; n++);
  for(i = 0; i < SIZE; i++)
  {
  printf("array[%d] = %d \n", i, array[i]);
  }
  return 0;
}


执行结果 :



octopus@octopus-Vostro-270s:~/code/c/pointer$ ./a.out 
123
123 43
674 1
array[0] = 123 
array[1] = 123 
array[2] = 43 
array[3] = 674 
array[4] = 1





5. 指针 和 数组





指针数组比较 :


-- 可互相替代 : 数组下标执行的操作都可以使用指针替代;


-- 效率比较 : 使用指针操作效率比数组要高;




指针 与 数组初始化 :


-- 声明数组 : int a[10]; 定义一个长度为10 的int数组;


-- 声明指针 : int *p; 定义一个指针, 该指针指向整型;


-- 相互赋值 : p = &a[0], 将数组第一个元素的地址赋值给指针变量;


-- 使用指针获取数组对象 : *p 等价于 a[0], *(p + 1) 等价于 a[1], *(p + i)等价于 a[i];


-- 注意地址的运算 : p + i , 在地址运算上, 每次增加 sizeof(int) * i 个字节;




将数组赋值给指针的途径 :


-- 将数组第一个元素地址赋值给指针变量 : p = &a[0];


-- 将数组地址赋值给指针变量 : p = a;




指针 和 数组 访问方式互换 : 前提 int *p, a[10]; p = a;


-- 数组计算方式 : 计算a[i]的时候, 先将数组转化为 *(a + i)指针, 然后计算该指针值;


-- 取值等价 : a[i] 等价于 *(p + i);


-- 地址等价 : &a[i] 与 a + i 是等价的;


-- 指针下标访问 : p[i] 等价于 *(p + i);


-- 结论 : 通过数组和下标 实现的操作 都可以使用 指针和偏移量进行等价替换;




指针 和 数组 的不同点 :


-- 指针是变量 : int *p, a[10]; p = a 和 p++ 没有错误;


-- 数组名不是变量 : int *p, a[10]; a = p 和 a++ 会报错;




数组参数 :


-- 形参指针 : 将数组传作为参数传递给函数的时候, 传递的是数组的首地址, 传递地址, 形参是指针;




数组参数示例 :


-- 函数参数是数组 : 函数传入一个字符串数组参数, 返回这个字符串长度;



/*************************************************************************
    > File Name: array_param.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: Sat 15 Mar 2014 12:46:57 AM CST
 ************************************************************************/
#include<stdio.h>
//计算字符串长度
int strlen(char *s)
{
        int n;
        for(n = 0; *s != '\0'; s++)
                n++;
        return n;
}
int main(int argc, char** argv)
{
        printf("strlen(djdhaj) = %d \n", strlen("djdhaj"));
        printf("strlen(12) = %d \n", strlen("12"));
        printf("strlen(dfe) = %d \n", strlen("dfe"));
}

-- 执行结果 : warning: conflicting types for built-in function ‘strlen’, 原因是 C语言中已经有了 strlen 函数了, 如果改一个函数名, 就不会有这个警告了;


[root@ip28 pointer]# gcc array_param.c 
array_param.c:12: warning: conflicting types for built-in function ‘strlen’
[root@ip28 pointer]# ./a.out           
strlen(djdhaj) = 6 
strlen(12) = 2 
strlen(dfe) = 3



数组和指针参数 : 将数组名传给参数, 函数根据情况判断是作为数组还是作为指针;


-- 实参 : 指针偏移量 和 数组下标 都可以作为 数组或指针函数形参, 如 数组情况fun(&array[2]) 或者 指针情况fun(p + 2);


-- 形参 : 函数的形参可以声明为 fun(int array[]), 或者 fun(int *array), 如果传入的是数组的第二个元素的地址, 可以使用array[-2]来获数组取第一个元素;




数组指针参数示例 :


/*************************************************************************
    > File Name: param_array_pointer.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: Sat 15 Mar 2014 01:28:33 AM CST
 ************************************************************************/
#include<stdio.h>
//使用指针做形参 取指针的前两位 和 当前位
void fun_p(int *p)
{
        printf("*(p - 2) = %d \n", *(p - 2));
        printf("*p = %d \n", *p);
}
//使用数组做形参 取数组的 第-2个元素 和 第0个元素
void fun_a(int p[])
{
        printf("p[-2] = %d \n", p[-2]);
        printf("p[0] = %d \n", p[0]);
}
int main(int argc, char **argv)
{
        int array[] = {1,2,3,4,5};
        //向指针参数函数中传入指针
        printf("fun_p(array + 2) : \n");
        fun_p(array + 2);
        //向数组参数函数中传入数组元素地址
        printf("fun_a(&array[2]) : \n");
        fun_a(&array[2]);
        //向指针参数函数中传入数组元素地址
        printf("fun_p(&array[2]) : \n");
        fun_p(&array[2]);
        //向数组参数函数中传入指针
        printf("fun_a(array + 2) : \n");
        fun_a(array + 2);
        return 0;
}


执行效果 :

[root@ip28 pointer]# gcc param_array_pointer.c 
[root@ip28 pointer]# ./a.out 
fun_p(array + 2) : 
*(p - 2) = 1 
*p = 3 
fun_a(&array[2]) : 
p[-2] = 1 
p[0] = 3 
fun_p(&array[2]) : 
*(p - 2) = 1 
*p = 3 
fun_a(array + 2) : 
p[-2] = 1 
p[0] = 3
目录
相关文章
|
5天前
|
移动开发 Android开发 数据安全/隐私保护
移动应用与系统的技术演进:从开发到操作系统的全景解析随着智能手机和平板电脑的普及,移动应用(App)已成为人们日常生活中不可或缺的一部分。无论是社交、娱乐、购物还是办公,移动应用都扮演着重要的角色。而支撑这些应用运行的,正是功能强大且复杂的移动操作系统。本文将深入探讨移动应用的开发过程及其背后的操作系统机制,揭示这一领域的技术演进。
本文旨在提供关于移动应用与系统技术的全面概述,涵盖移动应用的开发生命周期、主要移动操作系统的特点以及它们之间的竞争关系。我们将探讨如何高效地开发移动应用,并分析iOS和Android两大主流操作系统的技术优势与局限。同时,本文还将讨论跨平台解决方案的兴起及其对移动开发领域的影响。通过这篇技术性文章,读者将获得对移动应用开发及操作系统深层理解的钥匙。
|
22天前
|
存储 编译器 C语言
【C语言基础考研向】09 一维数组
数组是一种有序集合,用于存储相同类型的数据,便于统一操作与管理。例如,将衣柜底层划分为10个格子存放鞋子,便于快速定位。在C语言中,数组定义格式为 `类型说明符数组名[常量表达式];`,如 `int a[10];` 表示定义了一个包含10个整数的数组。数组初始化时可以直接赋值,也可以部分赋值,且数组长度必须固定。数组在内存中连续存储,访问时需注意下标范围,避免越界导致数据异常。数组作为参数传递时,传递的是首地址,修改会影响原数组。
|
22天前
|
存储 C语言
【C语言基础考研向】10 字符数组初始化及传递和scanf 读取字符串
本文介绍了C语言中字符数组的初始化方法及其在函数间传递的注意事项。字符数组初始化有两种方式:逐个字符赋值或整体初始化字符串。实际工作中常用后者,如`char c[10]=&quot;hello&quot;`。示例代码展示了如何初始化及传递字符数组,并解释了为何未正确添加结束符`\0`会导致乱码。此外,还讨论了`scanf`函数读取字符串时忽略空格和回车的特点。
|
25天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第六章 数组_一维数组_二维数组_字符数组详解
本章介绍了C语言中的数组概念及应用。数组是一种存储同一类型数据的线性结构,通过下标访问元素。一维数组定义需指定长度,如`int a[10]`,并遵循命名规则。数组元素初始化可使用 `{}`,多余初值补0,少则随机。二维数组扩展了维度,定义形式为`int a[3][4]`,按行优先顺序存储。字符数组用于存储字符串,初始化时需添加结束符`\0`。此外,介绍了字符串处理函数,如`strcat()`、`strcpy()`、`strcmp()` 和 `strlen()`,用于拼接、复制、比较和计算字符串长度。
|
1月前
|
JavaScript 前端开发 API
探索移动应用的世界:从开发到操作系统的深入解析
【8月更文挑战第31天】本文将带你走进移动应用的世界,从开发到操作系统,深入探讨移动应用的开发过程、移动操作系统的工作原理以及它们之间的交互。我们将通过代码示例,让你更好地理解移动应用的开发和运行机制。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和知识。
|
1月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
111 0
|
2月前
|
区块链 C# 存储
链动未来:WPF与区块链的创新融合——从智能合约到去中心化应用,全方位解析开发安全可靠DApp的最佳路径
【8月更文挑战第31天】本文以问答形式详细介绍了区块链技术的特点及其在Windows Presentation Foundation(WPF)中的集成方法。通过示例代码展示了如何选择合适的区块链平台、创建智能合约,并在WPF应用中与其交互,实现安全可靠的消息存储和检索功能。希望这能为WPF开发者提供区块链技术应用的参考与灵感。
46 0
|
2月前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
2月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
58 6
|
20天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理

推荐镜像

更多
下一篇
无影云桌面