C语言中指针和数组

指针

首先,明确一个概念,指针是什么,一旦提到这个老生常谈且富有争议性的话题,那真是1000个人有1000种看法。

在国内的很多教材中,给出的定义一般就是"指针就是地址",从初步理解指针的角度来说,这种说法是最容易理解的,但是这种说法明显有它的缺陷所在。
“指针就是地址"这种说法相当于"指针=字面值地址(或者说一个具体的右值)”,这种说法的错误所在就是弄错了指针的本质属性:指针是变量!

试想一下,如果指针是地址成立,那么二级指针怎么理解呢?地址的地址吗,这明显是错误的。

下面我们从指针是变量这个原则出发,来分析什么是指针:

  • 作为一个变量,肯定有自己的地址
  • 作为一个变量,肯定有自己的值,和普通变量的区别就是指针变量的值是地址。
  • 从第二点延伸过来,既然指针变量的值是地址,那么那个地址上的内容就是指针变量指向的数据,指针的类型就是指针变量指向数据的类型。
  • 指针有本身的类型,这个本身的类型区别于指向对象的类型。

在这里,最容易弄混的就是指针本身的类型和指针的类型,指针本身的类型是int型,一般情况下同一平台上所有类型指针都是一样的(注①),长度则是平台相关,一般情况下32位机中为4字节,64位机中为8字节,事实上,指针的大小由处理器中所使用的地址总线宽度决定,指针本身的类型有什么意义呢?
(为什么说一般情况下同一平台上所有类型指针都是一样,而不是所有情况呢?事实上,在某些地址总线宽度与数据总线宽度不同的特殊机器上指针类型可能不一致)

内存的访问是以字节为单位的,同时指针的值为一个地址,指针的类型就直接决定了指针的所能表示地址的上界和下界,32位指针访问范围为0~2^32字节,所以是4GB。
注:以下讨论中,对于指针指向数据的类型统一称为指针的类型,这篇博客主要讨论指针的类型而非指针本身的类型

而指针指向数据的类型则是在定义时指定的,比如int ptr,char str,在这里,ptr指针的数据类型就是int型,而str指针指向的类型是char型,区分指针指向数据的类型主要是用在对指针解引用时的不同,指针的值是具体的某一个位置,指向数据的不同则代表解引用的时候所取数据的不同,当ptr为int*类型时,表示在ptr表示的地址处取sizeof(int)个数据,依次类推。

指针的地址:如果一个指针变量存储的值是另一个指针的地址,那这个指针就是二级指针,同样的定义可以递推到多级指针。

指针的操作

解引用:用*来获取指针指向的数据,这个不用多说。
指针的运算:加减运算,需要注意的是,指针的加减运算的粒度是基于指针类型的长度,在下例中:

int *p = (int*)0x1000;
char *str = (char*)0x1000;
p++;
str++;
print("p=%d,str=%d\r\n",p,str);

输出结果:
p=0x1004,str=0x1001
可以看到,p指向int型数据,p++就相当于p+sizeof(int),而str++就相当于str+sizeof(char).

关于指针定义的争议

怎么样定义一个指针大家都知道,在编程时通常有两种写法:

int ptr;
int ptr;
咋一看,这俩不是一样吗?如果你仔细观察就可以发现其中的不同,第一种定义方法中靠近类型,而第二种靠近变量,看到这里,有些朋友就要说了,你个杠精!这不就是个写法问题吗,至于这么纠结吗!

这还真不仅仅是个写法问题。这两种写法背后代表着不同的逻辑:

  • 第一种写法的背后的逻辑是,将int作为一个整体,将其视为一个类型,即int、char*与int、char这些一样,都是一种独立的类型,再用这些类型来定义指针变量,从这个角度来看,指针是比较好理解的,而且看起来更能解释得通。
  • 第二种写法的背后逻辑是,在指针的定义中,仅仅是一个标识符,如int p,表明*后面所接的变量p是一个指针变量,指向数据类型为int型。
    其实在早期,大家一直都更倾向于通过第一种去理解指针,后来又有第二种看起来比较生涩的理解,为什么会这样呢?我们来看下面的例子:

int* p1,p2;
p2=p1;
我们来编译这个例子,结果是这样:

warning: assignment makes integer from pointer without a cast [-Wint-conversion]
编译信息显示,p2为普通int型变量,而p1是int型指针变量,这明显违背我们的初衷。如果要定义两个指针变量,我们应该这么做:

int p1,p2;
p2=p1;
相信到这里,大家能够看出来了,第一种写法背后逻辑的缺陷所在。

所以现在越来越多的专业书籍都推荐第二种写法,毕竟作为一门底层语言,严谨性比易读性要重要。

指针和数组的区别

废话说了那么多,我们来回到正题,看看指针和数组。不得不说,指针和数组就像孪生兄弟,有时候让人分不清楚,这种情况主要发生在函数参数传递的时候,当一个函数需要一个数组作为一个参数时,我们并不会将整个数组作为参数传递给函数,而是传入一个同类型指针p,然后在函数中就可以使用p[N]来访问数组中元素(这个大家都懂,就不放示例了)。

那么,指针和数组到底是不是同一个东西呢?
我们来看看下面的例子:

file1.c:
    int buf[10];
file2.c:
    extern int *buf;

编译结果:

error: conflicting types for ‘buf’。
从这里可以看出,数组和指针并不相等。

数据访问的本质区别

毫无疑问,我们经常使用指针的数组,也经常混用。但是我们有没有关注过它们背后的执行原理呢?我们看下面的代码:

int buf[10] = {5};
int *p = buf;
*p = 10;

首先,有必要来讲讲数组的初始化,在定义时,如果我们不对数组进行初始化操作,有两种情况:

  • 数组为全局变量或者静态变量时,在程序加载阶段默认所有元素都被初始化为0。
  • 数组为局部变量,因为数组数据在栈上分配,就延续了了栈上上一次的值,所以这个值是不确定的。

同时,我们可以对其进行初始化,可以全部初始化或者部分初始化,部分初始化时,未被初始化部分全部默认被初始化为0.所以我们常用buf[N]={0}来在定义时初始化一个数组。

根据C语言的规定,数组名=数组首元素指针,所以直接可以用数组名的解引用buf来访问第一个元素,也可以使用(buf+N)来访问第N个元素。

我们需要知道的是,在程序编译的时候,会对所有的变量分配一个地址,这个地址和变量的对应在符号表中被呈现,数组和指针在符号表中的区别就体现在这里:

  • 对于数组而言,符号表中存在的地址为数组首元素地址,所以当我们使用素组下标访问元素N时,它执行的是这样的操作
    1 先取出数组首元素地址
    2 目标地址=首地址+sizeof(type)N,得到被访问元素的地址,type是指针指向数据类型,指针加法参考上面。
    解引用(相当于在变量前加),从地址上取出被访问元素。
  • 对于指针变量而言,符号表中存储的是指针变量的地址,它访问元素时这样的过程:
    1 取出指针变量的地址,解引用以获取指针变量
    2 继续对指针变量进行解引用,获取目标元素的值。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值