c_lang_Reference_040820_03

本文主要面向Delphi程序员,详细介绍了C语言中数组指针变量和字符串指针变量的说明与使用。包括数组指针变量的定义、访问数组元素的两种方法,数组名和指针变量作函数参数的情况,多维数组指针变量的地址表示及定义,还阐述了字符串指针变量的使用及与字符数组的区别。

文字来源 VC世界--C语言教室

自我存档,也献给和我一样的DElphi程序员


数组指针变量的说明和使用

  指向数组的指针变量称为数组指针变量。
    一个数组是由连续的一块内存单元组成的。
    数组名就是这块连续内存单元的首地址。
    一个数组也是由各个数组元素(下标变量) 组成的。
    每个数组元素按其类型不同占有几个连续的内存单元。
    一个数组元素的首地址也是指它所占有的几个内存单元的首地址。
    一个指针变量既可以指向一个数组,也可以指向一个数组元素, 可把数组名或第一个元素的地址赋予它。
    如要使指针变量指向第i号元素可以把i元素的首地址赋予它或把数组名加i赋予它。

数组指针变量说明的一般形式为:
类型说明符 * 指针变量名

引入指针变量后,就可以用两种方法来访问数组元素了。
  第一种方法为下标法,即用a[i]形式访问数组元素。 在第四章中介绍数组时都是采用这种方法。
  第二种方法为指针法,即采用*(pa+i)形式,用间接访问的方法来访问数组元素。
main(){
int a[5],i,*pa;
pa=a;
for(i=0;i<5;i++){
*pa=i;
pa++;
}
pa=a;
for(i=0;i<5;i++){
printf("a[%d]=%d/n",i,*pa);
pa++;
}
}

下面,另举一例,该例与上例本意相同,但是实现方式不同。
main(){
int a[5],i,*pa=a;
for(i=0;i<5;){
*pa=i;
printf("a[%d]=%d/n",i++,*pa++);
}
}

数组名和数组指针变量作函数参数

数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址, 形参得到该地址后也指向同一数组。同样,指针变量的值也是地址, 数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。
float aver(float *pa);
main(){
float sco[5],av,*sp;
int i;
sp=sco;
printf("/ninput 5 scores:/n");
for(i=0;i<5;i++) scanf("%f",&sco[i]);
av=aver(sp);
printf("average score is %5.2f",av);
}
float aver(float *pa)
{
int i;
float av,s=0;
for(i=0;i<5;i++) s=s+*pa++;
av=s/5;
return av;
}

指向多维数组的指针变量

本小节以二维数组为例介绍多维数组的指针变量。

一、多维数组地址的表示方法
设有整型二维数组a[3][4]如下:
0 1 2 3
4 5 6 7
8 9 10 11
  设数组a的首地址为1000,各下标变量的首地址及其值如图所示。在第四章中介绍过, C语言允许把一个二维数组分解为多个一维数组来处理。因此数组a可分解为三个一维数组,即a[0],a[1],a[2]。每一个一维数组又含有四个元素。例如a[0]数组,含有a[0][0],a[0][1],a[0][2],a[0][3]四个元素。 数组及数组元素的地址表示如下:a是二维数组名,也是二维数组0行的首地址,等于1000。a[0]是第一个一维数组的数组名和首地址,因此也为1000。*(a+0)或*a是与a[0]等效的, 它表示一维数组a[0]0 号元素的首地址。 也为1000。&a[0][0]是二维数组a的0行0列元素首地址,同样是1000。因此,a,a[0],*(a+0),*a,&a[0][0]是相等的。同理,a+1是二维数组1行的首地址,等于1008。a[1]是第二个一维数组的数组名和首地址,因此也为1008。 &a[1][0]是二维数组a的1行0列元素地址,也是1008。因此a+1,a[1],*(a+1),&a[1][0]是等同的。 由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的。 此外,&a[i]和a[i]也是等同的。因为在二维数组中不能把&a[i]理解为元素a[i]的地址,不存在元素a[i]。

  C语言规定,它是一种地址计算方法,表示数组a第i行首地址。由此,我们得出:a[i],&a[i],*(a+i)和a+i也都是等同的。另外,a[0]也
可以看成是a[0]+0是一维数组a[0]的0号元素的首地址, 而a[0]+1则是a[0]的1号元素首地址,由此可得出a[i]+j则是一维数组a[i]的j号元素首地址,它等于&a[i][j]。由a[i]=*(a+i)得a[i]+j=*(a+i)+j,由于*(a+i)+j是二维数组a的i行j列元素的首地址。该元素的值等于*(*(a+i)+j)。
[Explain]#define PF "%d,%d,%d,%d,%d,/n"
main(){
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
printf(PF,a,*a,a[0],&a[0],&a[0][0]);
printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);
printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);
printf("%d,%d/n",a[1]+1,*(a+1)+1);
printf("%d,%d/n",*(a[1]+1),*(*(a+1)+1));
}

二、多维数组的指针变量

  把二维数组a 分解为一维数组a[0],a[1],a[2]之后,设p为指向二维数组的指针变量。可定义为: int (*p)[4] 它表示p是一个指针变量,它指向二维数组a 或指向第一个一维数组a[0],其值等于a,a[0],或&a[0][0]等。而p+i则指向一维数组a[i]。从前面的分析可得出*(p+i)+j是二维数组i行j 列的元素的地址,而*(*(p+i)+j)则是i行j列元素的值。

  二维数组指针变量说明的一般形式为: 类型说明符 (*指针变量名)[长度] 其中“类型说明符”为所指数组的数据类型。“*”表示其后的变量是指针类型。 “长度”表示二维数组分解为多个一维数组时, 一维数组的长度,也就是二维数组的列数。应注意“(*指针变量名)”两边的括号不可少,如缺少括号则表示是指针数组(本章后面介绍),意义就完全不同了。
[Explain]main(){
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0;i<3;i++)
for(j=0;j<4;j++) printf("%2d ",*(*(p+i)+j));
}
'Expain字符串指针变量的说明和使用字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。只能按对指针变量的赋值不同来区别。 对指向字符变量的指针变量应赋予该字符变量的地址。如: char c,*p=&c;表示p是一个指向字符变量c的指针变量。而: char *s="C Language";则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。
请看下面一例。
main(){
char *ps;
ps="C Language";
printf("%s",ps);
}
运行结果为:
C Language
上例中,首先定义ps是一个字符指针变量, 然后把字符串的首地址赋予ps(应写出整个字符串,以便编译系统把该串装入连续的一块内存单元),并把首地址送入ps。程序中的: char *ps;ps="C Language";等效于: char *ps="C Language";输出字符串中n个字符后的所有字符。
main(){
char *ps="this is a book";
int n=10;
ps=ps+n;
printf("%s/n",ps);
}
运行结果为:
book 在程序中对ps初始化时,即把字符串首地址赋予ps,当ps= ps+10之后,ps指向字符“b”,因此输出为"book"。
main(){
char st[20],*ps;
int i;
printf("input a string:/n");
ps=st;
scanf("%s",ps);
for(i=0;ps[i]!='/0';i++)
if(ps[i]=='k'){
printf("there is a 'k' in the string/n");
break;
}
if(ps[i]=='/0') printf("There is no 'k' in the string/n");
}
  本例是在输入的字符串中查找有无‘k’字符。 下面这个例子是将指针变量指向一个格式字符串,用在printf函数中,用于输出二维数组的各种地址表示的值。但在printf语句中用指针变量PF代替了格式串。 这也是程序中常用的方法。
main(){
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
char *PF;
PF="%d,%d,%d,%d,%d/n";
printf(PF,a,*a,a[0],&a[0],&a[0][0]);
printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);
printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);
printf("%d,%d/n",a[1]+1,*(a+1)+1);
printf("%d,%d/n",*(a[1]+1),*(*(a+1)+1));
}
  在下例是讲解,把字符串指针作为函数参数的使用。要求把一个字符串的内容复制到另一个字符串中,并且不能使用strcpy函数。函数cprstr的形参为两个字符指针变量。pss指向源字符串,pds指向目标字符串。表达式:
(*pds=*pss)!=`/0'
cpystr(char *pss,char *pds){
while((*pds=*pss)!='/0'){
pds++;
pss++; }
}
main(){
char *pa="CHINA",b[10],*pb;
pb=b;
cpystr(pa,pb);
printf("string a=%s/nstring b=%s/n",pa,pb);
}
  在上例中,程序完成了两项工作:一是把pss指向的源字符复制到pds所指向的目标字符中,二是判断所复制的字符是否为`/0',若是则表明源字符串结束,不再循环。否则,pds和pss都加1,指向下一字符。在主函数中,以指针变量pa,pb为实参,分别取得确定值后调用cprstr函数。由于采用的指针变量pa和pss,pb和pds均指向同一字符串,因此在主函数和cprstr函数中均可使用这些字符串。也可以把cprstr函数简化为以下形式:
cprstr(char *pss,char*pds)
{while ((*pds++=*pss++)!=`/0');}
  即把指针的移动和赋值合并在一个语句中。 进一步分析还可发现`/0'的ASCⅡ码为0,对于while语句只看表达式的值为非0就循环,为0则结束循环,因此也可省去“!=`/0'”这一判断部分,而写为以下形式:
cprstr (char *pss,char *pds)
{while (*pdss++=*pss++);}
表达式的意义可解释为,源字符向目标字符赋值, 移动指针,若所赋值为非0则循环,否则结束循环。这样使程序更加简洁。简化后的程序如下所示。
cpystr(char *pss,char *pds){
while(*pds++=*pss++);
}
main(){
char *pa="CHINA",b[10],*pb;
pb=b;
cpystr(pa,pb);
printf("string a=%s/nstring b=%s/n",pa,pb);
}

使用字符串指针变量与字符数组的区别

用字符数组和字符指针变量都可实现字符串的存储和运算。 但是两者是有区别的。在使用时应注意以下几个问题:

1. 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘/0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。

2. 对字符数组作初始化赋值,必须采用外部类型或静态类型,如: static char st[]={“C Language”};而对字符串指针变量则无此限制,如: char *ps="C Language";

3. 对字符串指针方式 char *ps="C Language";可以写为: char *ps; ps="C Language";而对数组方式:
static char st[]={"C Language"};
不能写为:
char st[20];st={"C Language"};
而只能对字符数组的各元素逐个赋值。

  从以上几点可以看出字符串指针变量与字符数组在使用时的区别,同时也可看出使用指针变量更加方便。前面说过,当一个指针变量在未取得确定地址前使用是危险的,容易引起错误。但是对指针变量直接赋值是可以的。因为C系统对指针变量赋值时要给以确定的地址。因此,
char *ps="C Langage";
或者 char *ps;
ps="C Language";都是合法的。

<think> 在Java中,并没有直接等同于C/C++的`__FUNCTION__`宏的内置功能。不过,Java提供了一些机制来获取类似的信息,主要是通过`StackTraceElement`类或者使用反射API。以下是几种常见的方法: 1. **使用`Thread.currentThread().getStackTrace()`** 可以通过当前线程的堆栈跟踪来获取当前执行的方法名。 示例代码: ```java public class FunctionNameExample { public static void main(String[] args) { String methodName = new Object(){}.getClass().getEnclosingMethod().getName(); System.out.println("Current method: " + methodName); // 输出:main new FunctionNameExample().exampleMethod(); } public void exampleMethod() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println("Current method: " + methodName); // 输出:exampleMethod } } ``` 注意:`Thread.currentThread().getStackTrace()`返回的是一个`StackTraceElement`数组,其中索引0表示栈顶(当前方法),索引1表示调用当前方法的方法(即上一层)。但要注意,不同的JVM实现可能有所不同,所以通常我们通过遍历堆栈来查找合适的位置。 一种更简洁的方式(在Java 9+中推荐使用`StackWalker`): ```java import java.lang.StackWalker; public class StackWalkerExample { public static void main(String[] args) { new StackWalkerExample().exampleMethod(); } public void exampleMethod() { StackWalker walker = StackWalker.getInstance(); String methodName = walker.walk(frames -> frames.findFirst().map(StackWalker.StackFrame::getMethodName).orElse("Unknown")); System.out.println("Current method: " + methodName); // 输出:exampleMethod } } ``` 2. **使用`Throwable`** 也可以创建一个`Throwable`对象(但不抛出)来获取堆栈信息。 示例代码: ```java public class ThrowableExample { public void exampleMethod() { String methodName = new Throwable().getStackTrace()[0].getMethodName(); System.out.println("Current method: " + methodName); } } ``` 3. **使用匿名内部类(在静态方法中)** 对于静态方法,可以使用匿名内部类来获取方法名(但这种方式只能用于非静态上下文中获取当前方法名): ```java public class AnonymousClassExample { public static void main(String[] args) { new AnonymousClassExample().exampleMethod(); } public void exampleMethod() { String methodName = new Object(){}.getClass().getEnclosingMethod().getName(); System.out.println("Current method: " + methodName); } } ``` 4. **使用AOP(面向切面编程)** 在更复杂的场景中,可以使用AOP框架(如AspectJ)在方法执行时自动捕获方法名。这种方法通常用于日志记录等横切关注点。 **总结**: - 在Java中,获取当前方法名最常用的方式是通过`Thread.currentThread().getStackTrace()`或`Throwable`的堆栈跟踪。 - 在Java 9及以上版本,推荐使用`StackWalker`,因为它更高效且更安全。 - 注意,这些方法都有一定的性能开销,因此在性能敏感的代码中应谨慎使用。 如果你需要在日志中输出当前方法名,可以考虑使用日志框架(如SLF4J、Log4j2)的占位符功能,它们通常提供了方便的方法来输出方法名。例如,在Log4j2中可以使用`%method`。 §§相关问题§§ 1. Java 9的`StackWalker`相比传统的堆栈跟踪方法有什么优势? 2. 如何在Java中获取调用者的方法名? 3. Java中是否有类似C/C++的`__LINE__`和`__FILE__`的功能? </think> 在Java中,并没有直接等同于C/C++的`__FUNCTION__`宏的内置功能,但可以通过以下方式实现类似效果: ### 1. 获取方法名(推荐方式) 使用`StackTraceElement`获取当前执行的方法名: ```java public class FunctionName { public static void main(String[] args) { new FunctionName().printMethodName(); } public void printMethodName() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); System.out.println("当前方法: " + methodName); // 输出: printMethodName } } ``` ### 2. 使用反射获取方法名 通过`getEnclosingMethod()`获取封装方法名: ```java import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) throws Exception { Method method = ReflectionExample.class.getMethod("exampleMethod"); System.out.println("方法名: " + method.getName()); // 输出: exampleMethod } public static void exampleMethod() { // 通过匿名内部类获取 String name = new Object(){}.getClass().getEnclosingMethod().getName(); System.out.println("当前方法: " + name); // 输出: exampleMethod } } ``` ### 3. Java 9+ 的`StackWalker`(最佳实践) Java 9引入了更高效的`StackWalker` API: ```java import java.lang.StackWalker; public class StackWalkerExample { public static void main(String[] args) { new StackWalkerExample().logMethodName(); } public void logMethodName() { StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); String methodName = walker.walk(frames -> frames.findFirst().map(StackWalker.StackFrame::getMethodName).orElse("未知方法") ); System.out.println("执行方法: " + methodName); // 输出: logMethodName } } ``` ### 关键区别说明 | 特性 | C/C++ `__FUNCTION__` | Java 替代方案 | |---------------------|---------------------------|------------------------------| | **获取方式** | 预处理器宏 | 运行时反射/堆栈跟踪 | | **性能影响** | 零开销 | 有运行时开销 | | **返回内容** | 当前函数名 | 当前方法名 | | **编译时确定** | ✓ | ✗ (运行时确定) | | **线程安全** | ✓ | ✓ | ### 实际应用场景 1. **日志记录**:自动添加调用方法名 ```java void processData() { logger.debug("进入方法: {}", getCurrentMethodName()); // ...业务逻辑... } ``` 2. **调试信息**:异常处理中自动添加上下文 ```java try { riskyOperation(); } catch (Exception e) { throw new RuntimeException("在" + getMethodName() + "中出错", e); } ``` 3. **AOP编程**:结合Spring AOP实现自动方法追踪 ```java @Aspect public class LoggingAspect { @Before("execution(* com.example..*.*(..))") public void logMethod(JoinPoint joinPoint) { System.out.println("调用方法: " + joinPoint.getSignature().getName()); } } ``` > **性能提示**:在生产环境中频繁获取方法名会影响性能,建议仅在调试/日志场景使用。对于高性能场景,可使用`-XX:-OmitStackTraceInFastThrow` JVM参数优化[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值