转]C语言灵魂——指针

转]C语言灵魂——指针  (2011-01-18 10:09:28)
标签: 

it

分类: C/CPP

原作者:wuliming wuliming_sc@163.com

 

指针与数组1(c缺陷与陷阱3.1)

c语言中的数组值得注意的地方有以下两点:

1c语言中只有一维数组而且数组的大小必须在编译期间就作为一个常数确定下来(C99标准允许变长数组,GCC编译器中实现了变长数组)。然而,c语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要仿真出一个多维数组就不是一件难事。

2、对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。

 

现在考虑下面的例子:

int i;

int *p;

int calendar[12][31];

上面声明的calendar是一个数组,该数组拥有12个数组类型的元素,其中的每个元素都是一个拥有31个整型元素的数组。因此,sizeof(calendar)的值是:31×12×sizeof(int)

 

考虑一下,calendar[4]的含义是什么?因为calender是一个有着12个数组类型元素的数组,它的每个数组类型元素又是一个有着31个整型元素的数组,所以calendar[4]calendar数组的第5个元素,是calendar数组中12个有着31个整型元素的数组之一。因此,calendar[4]的行为也表现为一个有着31个整型元素的数组的行为。例如,sizeof(calendar[4])的结果是:31×sizeof(int)

 

又如,p = calendar[4];这个语句使指针p指向了数组calendar[4]中下标为0的元素。因为calendar[4]是一个数组,我们可以通过下标的形式来指定这个数组中的元素:i = calendar[4][7],这个语句也可以写成下面这样而表达的意思保持不变i = *( calendar[4] + 7 ),还可以进一步写成:i = *( *( calendar + 4 ) + 7 )

 

下面我们再看:p = calendar; 这个语句是非法的,因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针。而p是一个指向整型变量的指针,两个指针的类型不一样,所以是非法的。显然,我们需要一种声明指向数组的指针的方法。

 

int calendar[12][31];

int (*monthp)[31];

monthp = calendar;

int (*monthp)[31] 语句声明的 *monthp 是一个拥有31个整型元素的数组,因此,monthp就是一个指向这样的数组的指针。monthp指向数组calendar的第一个元素。

 

指针与数组2(c和指针.P141.)

·1、数组的名的值是一个指针常量,不能试图将一个地址赋值给数组名;

·2、当数组名作为sizeof操作符的操作数时,sizeof(arrayname)返回的是整个数组的长度,而不是指向数组的指针的长度;

·3、当数组名作为单目操作符&的操作数,取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

·4、指针和数组并不总是相等的。为了说明这个概念,请考虑下面这两个声明:

int a[5];

int *b;

ab能够互换吗?它们都具有指针值,它们都可以进行间接访问和下标操作。但是,它们还是有很大的区别的:声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。把这两个声明用图的方法表示,可以发现它们之间存在显著的不同:

        a

 

 

 

 

 

        b

    因此,上述声明后,表达式*a是完全合法的,但表达式*b却是非法的。*b将访问内存中某个不确定的位置,或者导致程序终止。另一方面,表达式b++可以通过编译,但是a++却不能,因为a的值是一个常量。

 

#include<stdio.h>

 

int main()

{

     //注意sizeof(num)的长度应该为10*4=40

         int num[] = {0,1,2,3,4,5,6,7,8,9};

         printf(" sizeof(num) = %d\n", sizeof(num) );

 

         //注意sizeof(str)的长度应该为11,包括字符串后面的'\0'

         char str[] = "0123456789";

         printf(" sizeof(str) = %d\n", sizeof(str) );

 

         //注意sizeof(str1)的长度应该为10,不包括字符串后面的'\0',但是,最好将字符串的最后一个字符设定为空

         char str1[] = {'0','1','2','3','4','5','6','7','8','9'};

         printf(" sizeof(str1) = %d\n", sizeof(str1) );

 

         //&num的类型为'int (*)[10]',表示的是一个指向长度为10的整形数组的指针

         int (*ptoint)[10] = &num;

         printf(" sizeof(ptoint) = %d, (*ptoint)[9] = %d\n", sizeof(ptoint), (*ptoint)[9] );

 

         //&str的类型为'char (*)[11]',表示的是一个指向长度为11的字符数组的指针,注意str数组的长度是11,而不是10

         char (*ptostr)[11] = &str;

         printf(" sizeof(ptostr) = %d, (*ptostr)[9] = %c\n", sizeof(ptostr), (*ptostr)[9] );

 

         //由于p指向的是数组num[5],所以对下标取负值后,不会超出数组的正常取值范围

         //该例子也说明了为什么下标检查在c语言中是一项困难的任务:下标引用可以作用于任意的指针,而不仅仅是数组名

         //作用于指针的下标引用的有效性即依赖于该指针当时恰好指向什么内容,也依赖于下标的值

         int *p = num + 5;

         printf(" p[-1] = %d, p[0] = %d, p[1] = %d \n", p[-1],p[0],p[1] );

 

         //下面的表达式中,'num[5]5[num]'的值是一样的,把它们转换成对等的间接访问表达式,它们都等同于*(num + 2)

         //'5[num]'这个古怪的表达式之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别

         //但是,决不应该编写形如'5[num]'的表达式,因为它会大大的影响程序的可读性

         printf(" num[5] = %d, 5[num] = %d \n", num[5], 5[num] );

     getchar();

         return 0;

}

输出结果为: [转]C语言灵魂——指针 

 

 

指针和数组的相同与不同(c专家编程.P199.)

在实际应用中,数组和指针可以互换的情形要比两者不可互换的情形更为常见。让我们分别考虑“声明”和“使用”这两种情况。声明本身还可以进一步分为3种情况:

·外部数组的声明;

·数组的定义(定义是声明的一种特殊情况,它分配内存空间,并可能提供一个初始值);

·函数参数的声明;

 

[转]C语言灵魂——指针

也既是:
作为函数参数时、在语句或表达式中使用数组时,我们可以采用数组或者指针的任何一种形式,除此之外的其他情况下,指针和数组不要互换。下面就数组和指针相同的情况做详细的说明:

 

·规则1、表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。

假如我们声明:

int a[10];

int *p = a;

就可以通过一下任何一种方式来访问a[i]

p[i]       *( p + i )        *( a + i )      ·····

事实上,可以采用的方法很多。对数组的引用如a[i] 在编译时总是被编译器改写成*(a+i)的形式,C语言标准要求编译器必须具备这个概念性的行为。

编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是4个字节,那么a[i+1]a[i]在内存中的距离就是4。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在,因为编译器需要知道对指针进行解除引用操作时应该取几个字节,以及每个下标的步长应取几个字节。

 

·规则2、下标总是和指针的偏移量相同。

把数组下标作为指针加偏移量是c语言从BCPL(C语言的祖先)继承过来的技巧。在人们的常规思维中,在运行时增加对c语言下标的范围检查是不切实际的。因为取下标操作只是表示将要访问该数组,但并不保证一定要访问。而且程序员完全可以使用指针来访问数组,从而绕过下标操作符。在这种情况下,数组下标范围检测并不能检测所有对数组的访问的情况。事实上,下标范围检测被认为不值得加入到c语言当中。

还有一个说法是,在编写数组算法时,使用指针比使用数组更有效率。这个颇为人们所接收的说法在通常情况下是错误的。使用现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著的差别。不管怎样,数组下标是定义在指针的基础上,所以优化器常常可以把它转化为更有效率的指针表达式,并生成相同的机器指令。

 

 

·规则3、在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。

    在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。这种转换意味着在声明函数的时候,以下三种形式都是合法的(同时无论实参是数组还是真的指针也都是合法的)

my_function( int *turnip ) {···}

my_function( int turnip[] ) {···}

my_function( int turnip[200] ) {···}

 

array_name&array_name的异同

前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。
char a[MAX];        //array of MAX characters
char *p = a;         //p
为指向数组的指针
char *pa = &a;      //
该语句是不正确的,pa的类型为'char *',而&a的类型为'char (*)[MAX]'

char (*pb)[MAX] = &a;       //该语句是正确的,pb的类型为'char (*)[MAX]'  

 

#include<stdio.h>

 

void main()

{

char a[5] = {'a','b','c','d','\0'};   

char *p = a;    

 

//运行下面这句后, vc6.0 提示的错误为:cannot convert from 'char (*)[5]' to 'char *'&a的类型应该是指向一个数组的指针

//char *pa = &a;

//所以,应该定义一个指向相同类型和大小的数组的指针来获得“&a”的值

char (*point_to_str)[5];

   point_to_str = &a;

 

 printf("%d\n%d\n",&p, &point_to_str);

 printf("%s\n%s\n", p,  point_to_str);

}

 

运行结果为:

1245044

1245040

abcd

abcd

 

标题SpringBoot智能在线预约挂号系统研究AI更换标题第1章引言介绍智能在线预约挂号系统的研究背景、意义、国内外研究现状及论文创新点。1.1研究背景与意义阐述智能在线预约挂号系统对提升医疗服务效率的重要性。1.2国内外研究现状分析国内外智能在线预约挂号系统的研究与应用情况。1.3研究方法及创新点概述本文采用的技术路线、研究方法及主要创新点。第2章相关理论总结智能在线预约挂号系统相关理论,包括系统架构、开发技术等。2.1系统架构设计理论介绍系统架构设计的基本原则和常用方法。2.2SpringBoot开发框架理论阐述SpringBoot框架的特点、优势及其在系统开发中的应用。2.3数据库设计与管理理论介绍数据库设计原则、数据模型及数据库管理系统。2.4网络安全与数据保护理论讨论网络安全威胁、数据保护技术及其在系统中的应用。第3章SpringBoot智能在线预约挂号系统设计详细介绍系统的设计方案,包括功能模块划分、数据库设计等。3.1系统功能模块设计划分系统功能模块,如用户管理、挂号管理、医生排班等。3.2数据库设计与实现设计数据库表结构,确定字段类型、主键及外键关系。3.3用户界面设计设计用户友好的界面,提升用户体验。3.4系统安全设计阐述系统安全策略,包括用户认证、数据加密等。第4章系统实现与测试介绍系统的实现过程,包括编码、测试及优化等。4.1系统编码实现采用SpringBoot框架进行系统编码实现。4.2系统测试方法介绍系统测试的方法、步骤及测试用例设计。4.3系统性能测试与分析对系统进行性能测试,分析测试结果并提出优化建议。4.4系统优化与改进根据测试结果对系统进行优化和改进,提升系统性能。第5章研究结果呈现系统实现后的效果,包括功能实现、性能提升等。5.1系统功能实现效果展示系统各功能模块的实现效果,如挂号成功界面等。5.2系统性能提升效果对比优化前后的系统性能
在金融行业中,对信用风险的判断是核心环节之一,其结果对机构的信贷政策和风险控制策略有直接影响。本文将围绕如何借助机器学习方法,尤其是Sklearn工具包,建立用于判断信用状况的预测系统。文中将涵盖逻辑回归、支持向量机等常见方法,并通过实际操作流程进行说明。 一、机器学习基本概念 机器学习属于人工智能的子领域,其基本理念是通过数据自动学习规律,而非依赖人工设定规则。在信贷分析中,该技术可用于挖掘历史数据中的潜在规律,进而对未来的信用表现进行预测。 二、Sklearn工具包概述 Sklearn(Scikit-learn)是Python语言中广泛使用的机器学习模块,提供多种数据处理和建模功能。它简化了数据清洗、特征提取、模型构建、验证与优化等流程,是数据科学项目中的常用工具。 三、逻辑回归模型 逻辑回归是一种常用于分类任务的线性模型,特别适用于二类问题。在信用评估中,该模型可用于判断借款人是否可能违约。其通过逻辑函数将输出映射为0到1之间的概率值,从而表示违约的可能性。 四、支持向量机模型 支持向量机是一种用于监督学习的算法,适用于数据维度高、样本量小的情况。在信用分析中,该方法能够通过寻找最佳分割面,区分违约与非违约客户。通过选用不同核函数,可应对复杂的非线性关系,提升预测精度。 五、数据预处理步骤 在建模前,需对原始数据进行清理与换,包括处理缺失值、识别异常点、标准化数值、筛选有效特征等。对于信用评分,常见的输入变量包括收入水平、负债比例、信用历史记录、职业稳定性等。预处理有助于减少噪声干扰,增强模型的适应性。 六、模型构建与验证 借助Sklearn,可以将数据集划分为训练集和测试集,并通过交叉验证调整参数以提升模型性能。常用评估指标包括准确率、召回率、F1值以及AUC-ROC曲线。在处理不平衡数据时,更应关注模型的召回率与特异性。 七、集成学习方法 为提升模型预测能力,可采用集成策略,如结合多个模型的预测结果。这有助于降低单一模型的偏差与方差,增强整体预测的稳定性与准确性。 综上,基于机器学习的信用评估系统可通过Sklearn中的多种算法,结合合理的数据处理与模型优化,实现对借款人信用状况的精准判断。在实际应用中,需持续调整模型以适应市场变化,保障预测结果的长期有效性。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值