笔记整理--C语言

C语言指针5分钟教程

指针、引用和取值

什么是指针?什么是内存地址?什么叫做指针的取值?指针是一个存储计算机内存地址的变量。在这份教程里“引用”表示计算机内存地址。从指针指向的内存读取数据称作指针的取值。指针可以指向某些具体类型的变量地址,例如int、long和double。指针也可以是void类型、NULL指针和未初始化指针。本文会对上述所有指针类型进行探讨。

根据出现的位置不同,操作符 * 既可以用来声明一个指针变量,也可以用作指针的取值。当用在声明一个变量时,*表示这里声明了一个指针。其它情况用到*表示指针的取值。

&是地址操作符,用来引用一个内存地址。通过在变量名字前使用&操作符,我们可以得到该变量的内存地址。

// 声明一个int指针
int *ptr;
// 声明一个int值
int val = 1;
// 为指针分配一个int值的引用
ptr = &val;
// 对指针进行取值,打印存储在指针地址中的内容
int deref = *ptr;
printf("%d\n", deref);

第2行,我们通过*操作符声明了一个int指针。接着我们声明了一个int变量并赋值为1。然后我们用int变量的地址初始化我们的int指针。接下来对int指针取值,用变量的内存地址初始化int指针。最终,我们打印输出变量值,内容为1。

第6行的&val是一个引用。在val变量声明并初始化内存之后,通过在变量名之前使用地址操作符&我们可以直接引用变量的内存地址。

第8行,我们再一次使用*操作符来对该指针取值,可直接获得指针指向的内存地址中的数据。由于指针声明的类型是int,所以取到的值是指针指向的内存地址存储的int值。

这里可以把指针、引用和值的关系类比为信封、邮箱地址和房子。一个指针就好像是一个信封,我们可以在上面填写邮寄地址。一个引用(地址)就像是一个邮件地址,它是实际的地址。取值就像是地址对应的房子。我们可以把信封上的地址擦掉,写上另外一个我们想要的地址,但这个行为对房子没有任何影响。

void指针、NULL指针和未初始化指针

一个指针可以被声明为void类型,比如void *x。一个指针可以被赋值为NULL。一个指针变量声明之后但没有被赋值,叫做未初始化指针。

int *uninit; // int指针未初始化
int *nullptr = NULL; // 初始化为NULL
void *vptr; // void指针未初始化
int val = 1;
int *iptr;
int *castptr;
  
// void类型可以存储任意类型的指针或引用
iptr = &val;
vptr = iptr;
printf("iptr=%p, vptr=%p\n", iptr, vptr);
  
// 通过显示转换,我们可以把一个void指针转成
// int指针并进行取值
castptr = (int *)vptr;
printf("*castptr=%d\n", *castptr);
  
// 打印null和未初始化指针
printf("uninit=%p, nullptr=%p\n", uninit, nullptr);
// 不知道你会得到怎样的返回值,会是随机的垃圾地址吗?
// printf("*nullptr=%d\n", nullptr);
// 这里会产生一个段错误
// printf("*nullptr=%d\n", nullptr);

执行上面的代码,你会得到类似下面对应不同内存地址的输出。

iptr=0x7fff94b89c6c, vptr=0x7fff94b89c6c
*castptr=1
uninit=0x7fff94b89d50, nullptr=(nil)

第1行我们声明了一个未初始化int指针。所有的指针在赋值为NULL、一个引用(地址)或者另一个指针之前都是未被初始化的。第2行我们声明了一个NULL指针。第3行声明了一个void指针。第4行到第6行声明了一个int值和几个int指针。

第9行到11行,我们为int指针赋值为一个引用并把int指针赋值为void指针。void指针可以保存各种其它指针类型。大多数时候它们被用来存储数据结构。可以注意到,第11行我们打印了int和void指针的地址。它们现在指向了同样的内存地址。所有的指针都存储了内存地址。它们的类型只在取值时起作用。

第15到16行,我们把void指针转换为int指针castptr。请注意这里需要显示转换。虽然C语言并不要求显示地转换,但这样会增加代码的可读性。接着我们对castptr指针取值,值为1。

第19行非常有意思,在这里打印未初始化指针和NULL指针。值得注意的是,未初始化指针是有内存地址的,而且是一个垃圾地址。不知道这个内存地址指向的值是什么。这就是为什么不要对未初始化指针取值的原因。最好的情况是你取到的是垃圾地址接下来你需要对程序进行调试,最坏的情况则会导致程序崩溃。

NULL指针被初始化为o。NULL是一个特殊的地址,用NULL赋值的指针指向的地址为0而不是随机的地址。只有当你准备使用这个地址时有效。不要对NULL地址取值,否则会产生段错误。

 

指针和数组

C语言的数组表示一段连续的内存空间,用来存储多个特定类型的对象。与之相反,指针用来存储单个内存地址。数组和指针不是同一种结构因此不可以互相转换。而数组变量指向了数组的第一个元素的内存地址。

一个数组变量是一个常量。即使指针变量指向同样的地址或者一个不同的数组,也不能把指针赋值给数组变量。也不可以将一个数组变量赋值给另一个数组。然而,可以把一个数组变量赋值给指针,这一点似乎让人感到费解。把数组变量赋值给指针时,实际上是把指向数组第一个元素的地址赋给指针。

int myarray[4] = {1,2,3,0};
int *ptr = myarray;
printf("*ptr=%d\n", *ptr);
  
// 数组变量是常量,不能做下面的赋值
// myarray = ptr
// myarray = myarray2
// myarray = &myarray2[0]

第1行初始化了一个int数组,第2行用数组变量初始化了一个int指针。由于数组变量实际上是第一个元素的地址,因此我们可以把这个地址赋值给指针。这个赋值与*ptr = &myarray[0]效果相同,显示地把数组的第一个元素地址赋值到了ptr引用。这里需要注意的是,这里指针需要和数组的元素类型保持一致,除非指针类型为void。

 

指针与结构体

就像数组一样,指向结构体的指针存储了结构体第一个元素的内存地址。与数组指针一样,结构体的指针必须声明和结构体类型保持一致,或者声明为void类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct person {
  int age;
  char *name;
};
struct person first;
struct person *ptr;
  
first.age = 21;
char *fullname = "full name";
first.name = fullname;
ptr = &first;
  
printf("age=%d, name=%s\n", first.age, ptr->name);

第1至6行声明了一个person结构体,一个变量指向了一个person结构体和指向person结构体的指针。第8行为age成员赋了一个int值。第9至10行我们声明了一个char指针并赋值给一个char数组并赋值给结构体name成员。第11行我们把一个person结构体引用赋值给结构体变量。

第13行我们打印了结构体实例的age和name。这里需要注意两个不同的符号,’.’ 和 ‘->’ 。结构体实例可以通过使用 ‘.’ 符号访问age变量。对于结构体实例的指针,我们可以通过 ‘->’ 符号访问name变量。也可以同样通过(*ptr).name来访问name变量。

 

总结

希望这份简短的概述能够有助于了解不同的指针类型。在后续的博文中我们会探讨其它类型的指针和高级用法,比如函数指针。

欢迎提出提问并给出评论。

 

英文原文: Dennis Kubes   编译:伯乐在线 – 唐尤华

【如需转载,请标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

C语言深度剖析 (2013/2/27 9:36:09)




定义声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存。


register:寄存器变量,请求编译器将变量尽可能的将变量存放在CPU内部寄存器中而提高访问效率。

寄存器其实就是一块一块小的存储空间,只不过其存取速度比内存快。

register 变量必须是能被 CPU 寄存器所接受的类型。意味着 register 变量必须是一个单个的值,并且其长度应小于或等于整型的长度。 而且
register 变量可能不存放在内存中,所以不能用取址运算符“&”来获取 register 变量的地址。


static
的作用
:第一修饰变量,变量分为局部变量和全局变量,它们都在内存的静态区。

静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用 extern 声明也没法使用他。准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它。想要使用就得在前面再加
extern ***。恶心吧?要想不恶心,很简单,直接在文件顶端定义不就得了。

静态局部变量,在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他

函数也用不了。由于被 static
修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。

第二修饰函数,函数前加 static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。


sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时不能省略


#include <stdio.h>

include <string.h>

int main(int argc, char const argv[])
{
char a[1000];
int i;
for(i = 0; i < 1000; i++){
a[i] = -1-i;
}
printf("%d\n",strlen(a));
return 0;
}



1
2
3
4
5
6
7
8
9
10
11
12
按照我们上面的解释,那-0 和+0在内存里面分别怎么存储?
int i =  
    -20;
unsigned j = 10;
i+j的值为多少?为什么?
下面的代码有什么问题?
unsigned i ;
for (i=9;i>=0;i--)
{
  printf("%u\n",i);
}



bool变量与“零值”进行比较

bool bTestFlag = FALSE;

if(bTestFlag);

if(!bTestFlag);


float变量与“零值”进行比较

float fTestVal = 0.0;

if((fTestVal >= -EPSINON) && (fTestVal <= EPSINON)); //EPSINON
为定义好的

精度。


指针变量与“零值”进行比较

intp=NULL;

if(NULL == p);

if(NULL != p);



if、else一般表示两个分支或是嵌套表示少量的分支,但如果分支很多的话……还是用switch、case组合吧。


每个case 语句的结尾绝对不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠)。

最后必须使用default 分支。即使程序真的不需要 default 处理,也应该保留语句:

default :

          break;

这样做并非画蛇添足,可以避免让人误以为你忘了 default处理。


case 后面只能是整型或字符型的常量或常量表达式(想想字符型数据在内存里是怎么存的)


case语句的排列顺序:

  1.  

  2.  

    按字母或数字顺序排列各条case语句。


  3. 把正常情况放在前面,而把异常情况放在后面。

  4. 按执行频率排列case语句。

把default子句只用于检查真正的默认情况。



在 switch case 语句中能否使用
continue关键字?为什么?


循环语句的注意点

  1.  

  2.  

    在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。


  3. 建议for 语句的循环控制变量的取值采用“半开半闭区间”写法。

  4. 不能在for循环体内修改循环变量,防止循环失控。

  5. 循环要尽可能的短,要使代码清晰,一目了然。

  6. 把循环嵌套控制在3 层以内。

void真正发挥的作用在于:

(1) 对函数返回的限定;

(2) 对函数参数的限定。

任何类型的指针都可以直接赋值给它,无需进行强制类型转换。


void修饰函数返回值和参数

如果函数没有返回值,那么应声明为 void类型。

如果函数无参数,那么应声明其参数为void。


无论在 C
还是C++中,若函数不接受任何参数,一定要指明参数为 void。


void指针

  1.  

  2.  

    不能对  
    void指针进行算法操作。是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。也就是说必须知道内存目的地址的确切值。但是大名鼎鼎的
      GNU(GNU's Not Unix的递归缩写)则不这么认定,它指定 void 的算法


  3. 操作与 char 一致。

  4. 如果函数的参数可以是任意类型指针,那么应声明其参数为void

  5. void不能代表一个真实的变量。

  6. void体现了一种抽象,这个世界上的变量都是“有类型”的。

return用来终止一个函数并返回其后面跟着的值。

return 语句不可返回指向“栈内存”的“指针” ,因为该内存在函数体结束时被自动销毁。


定义 const只读变量,具有不可变性。

const修饰的只读变量必须在定义的同时初始化。

编译器通常不为普通
const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。


const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区)
,而#define定义的宏常量在内存中有若干个拷贝。#define宏是在预编译阶段进行替换,而
const修饰的只读变量是在编译的时候确定其值。#define宏没有类型,而 const修饰的只读变量具有特定的类型。

const修饰的只读变量不能用来作为定义数组的维数,也不能放在 case关键字后面。


  •  

  •  

    修饰一般变量

    这种只读变量在定义时,修饰符 const可以用在类型说明符前,也可以用在类型说明符后。例如:int const i=2; 或 const int
      i=2;


  • 修饰数组

    定义或说明一个只读数组可采用如下格式:int const a[5]={1, 2, 3, 4, 5};或const int a[5]={1, 2, 3,
      4, 5};

  • 修饰指针

    const int p; // p 可变,p指向的对象不可变

    int const p; // p可变,p指向的对象不可变

    intconstp; //p不可变,p指向的对象可变

    const int const p; //指针 p和 p指向的对象都不可变


    先忽略类型名,看  
    const离哪个近,离谁近就修饰谁。

              const int p;
//const修饰
p,p是指针,
p是指针指向的对象,不可变

              int const
p;//const修饰p,p是指针,
p是指针指向的对象,不可变

              int const  p;//const修饰
p,p不可变,p 指向的对象可变

              const int const p; //前一个
const修饰
p,后一个 const修饰p,指针 p和 p 指向的对象都不可变

     

  • 修饰函数的参数

    const修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:void Fun(const int  
    i);告诉编译器i在函数体中的不能改变, 从而防止了使用者的一些无意的或错误的修改。

  • 修饰函数的返回值

    const修饰符也可以修饰函数的返回值,返回值不可被改变。例如:const int Fun (void);

最易变的关键字----volatile

用它修饰的变量表示可以被某些编译器,未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。



最会带帽子的关键字----extern

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中。


struct 关键字

结构体所占的内存大小是其成员所占内存之和,空结构体的大小就定位 1 个byte。


struct与 class的区别

在 C++里 struct关键字与 class关键字一般可以通用,只有一个很小的区别。struct的成员默认情况下属性是
public的,而class成员却是 private的。


在union中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。

一个 union只配置一个足够大的空间以来容纳最大长度的数据成员。


大端模式和小端模式。

大端模式(Big_endian) :字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。

小端模式(Little_endian) :字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。


union型数据所占的空间等于其最大的成员所占的空间。

对 union型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始, 也就是联合体的访问不论对哪个变量的存取都是从
union的首地址位置开始。


请写一个 C 函数,若处理器是Big_endian的,则返回 0;若是Little_endian的,则返回 1。

利用 union类型数据的特点:所有成员的起始地址一致。


枚举与#define宏的区别

  1.  

  2.  

    #define宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。


  3. 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。

  4. 枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个。

注释的使用规则

编译器的确会将注释剔除,但不是简单的剔除,而是用空格代替原来的注释。



y=x/p   表示把x赋值给y,同时注释开始

y=x/(p) 表示把x除以 p 指向的内存里的值,把结果赋值为 y

只要斜杠(/)和星号()之间没有空格,都会被当作注释的开始。这一点一定要注意。


C语言里以反斜杠(\)表示断行。
编译器会将反斜杠剔除掉,跟在反斜杠后面的字符自动接续到前一行。但是注意:反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格。
反斜杠除了可以被用作接续符,还能被用作转义字符的开始标识。
按位异或操作可以实现不用第三个临时变量交换两个变量的值:
a=b;b=a;a^=b;
左移和右移
左移运算符“<<”是双目运算符。其功能把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补
0。
右移运算符“>>”是双目运算符。其功能是把“>>”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。但注意:对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补 0;而为负数时,符号位为
1,最高位是补0 或是补1 取决于编译系统的规定。TurboC和很多系统规定为补 1。
左移和右移的位数是有讲究的。左移和右移的位数不能大于数据的长度,不能小于 0。  




宏函数被调用时是以实参代换形参。而不是“值传送” 。


内存的自然边界分别是偶数地址,可以被 4整除的地址,和可以被 8 整除的地址。
对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个。
#pragmapack(8)
structTestStruct4
{
   char a;
   long b;
};
structTestStruct5
{
   char c;
   TestStruct4 d;
   long long e;
};
#pragmapack()
问题:
A),sizeof(TestStruct5)=?
B),TestStruct5的 c后面空了几个字节接着是 d?
TestStruct4中,成员 a是 1字节默认按 1字节对齐,指定对齐参数为8,这两个值中取1,a按 1 字节对齐;成员 b是 4个字节,默认是按
4字节对齐,这时就按4 字节对齐,所以sizeof(TestStruct4)应该为 8;
TestStruct5 中,c和 TestStruct4 中的 a 一样,按 1字节对齐,而 d 是个结构,它是8
个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,TestStruct4的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了
4 个字节的空,从第 16个字节开始放置成员 e.这时,长度为24,已经可以被 8(成员 e按 8字节对齐)整除.这样,一共使用了 24个字节.内存布局如下
(表示空闲内存, 1表示使用内存。单位为 1byete) :
                   
  a    b
TestStruct4的内存布局:1,1111,
                   
   c     TestStruct4.a    TestStruct4.b  
 d
TestStruct5的内存布局: 1, 1,          
 1111,
,      11111111
##预算符
和#运算符一样,##运算符可以用于宏函数的替换部分。这个运算符把两个语言符号组合成单个语言符号。看例子:
#define XNAME(n) x##n
如果这样使用宏:
XNAME(8)
则会被展开成这样:
x8
看明白了没?##就是个粘合剂,将前后两部分粘合起来。
只能给非只读变量赋值。


指针和数组

指针就是指针,指针变量在 32位系统下,永远占 4个
byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数。
A), char p =“abcdef”;
B), char a[] =“123456”;
以指针的形式访问和以下标的形式访问指针
例子 A)定义了一个指针变量 p,p 本身在栈上占4 个byte,p 里存储的是一块内存的首地址。这块内存在静态区,其空间大小为 7个
byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问。比如现在需要读取字符‘e’ ,我们有两种方式:
1)以指针的形式:(p+4)。先取出 p里存储的地址值,假设为 0x0000FF00,然后加上 4 个字符的偏移量,得到新的地址
0x0000FF04。然后取出 0x0000FF04地址上的值。
2)以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出
p里存储的地址值,然后加上中括号中
4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。
以指针的形式访问和以下标的形式访问数组
例子 B)定义了一个数组 a,a拥有 7个 char类型的元素,其空间大小为 7。数组a本身在栈上面。对 a的元素的访问必须先根据数组的名字
a找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的“具名+匿名”访问。比如现在需要读取字符‘5’ ,我们有两种方式:
1)以指针的形式: (a+4)。 a 这时候代表的是数组首元素的首地址, 假设为 0x0000FF00,然后加上 4 个字符的偏移量,得到新的地址
0x0000FF04。然后取出 0x0000FF04地址上的值。
2)以下标的形式:a[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。a[4]这个操作会被解析成:a作为数组首元素的首地址,然后加上中括号中
4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。
由上面的分析,我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。一定要注意的是这个“以
XXX的形式的访问”这种表达方式。另外一个需要强调的是:上面所说的偏移量 4 代表的是4 个元素,而不是 4 个byte。只不过这里刚好是 char类型数据
1个字符的大小就为 1个 byte。记住这个偏移量的单位是元素的个数而不是
byte数,在计算新地址时千万别弄错了。
指针数组和数组指针
初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在 32位系统下永远是占
4个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢:
A),int  p1[10];
B),int  (p2)[10];
每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。“[]”的优先级比“”要高。p1先与“[]”结合,构成一个数组的定义,数组名为
p1,int
修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含 10个指向 int类型数据的指针,即指针数组。至于
p2就更好理解了,在这里“ () ”的优先级比“[]”高, “”号和 p2构成一个指针的定义,指针变量名为
p2,int修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚 p2是一个指针,它指向一个包含 10个
int类型数据的数组,即数组指针。

数组指针(也称行指针)
定义
int (
p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

如要将二维数组赋给一指针,应这样赋值:
int
a[3][4];
int
(p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a;
       //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++;
      //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

指针数组
定义
int p[n];
[]优先级高,先与p结合成为一个数组,再由int
说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样
p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int
p[3];
int
a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int
p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
(p[i]+j)、
(
(p+i)+j)、((p+i))[j]、p[i][j]


优先级:()>[]>

数组参数与指针参数
C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。
函数的返回值也不能是一个数组,而只能是指针。
C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。
(int)&p----这是什么?
&p 是求指针变量 p本身的地址,这是一个 32位的二进制常数(32位系统)
(int)&p表示将地址强制转换成指向
int类型数据的指针。
(int)&p =
(int)Function;表示将函数的入口地址赋值给指针变量 p。
使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。
栈、堆和静态区
  1. 静态区:保存自动全局变量和 static变量(包括 static全局和局部变量)
      。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配。

  2. 栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。

  3. 堆:由 malloc系列函数或 new操作符分配的内存。其生命周期由 free或
      delete决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。

free完之后,一定要给指针置 NULL。
return
语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁。
不使用任何变量编写strlen函数
intmy_strlen(constcharstrDest)
{
 
 assert(NULL!=strDest);
 
 return('\0'!=strDest)?(1+my_strlen(strDest+1)):0;
}


文件操作 (2013/2/26 15:07:24)

 
 
  
 
 
 
 
 
 
 
 

数据链表与内存分配 (2013/2/26 14:50:13)

 
 
 

链表

 
 

结构体,共用体 (2013/2/26 12:24:47)

  
  
 
 

位运算

 
 

字符与字符串 (2013/2/25 21:49:09)

  
 

指针 (2013/2/25 21:13:44)

   
  

数组名是数组的首地址是一个常量

数组的名称是首地址,但是是常量地址,不能改变,不能赋值;

 

 

 

 

 
 
指针相关运算总结
 
  
 
 

C基础 (2013/2/25 20:37:39)

 
 
 

auto: 花括号内的都是auto型变量,局部变量,现在很少使用,都是缺省了<o:p></o:p>

extern:具有外部变量链接;如果在别的文件中定义了变量如:int ER; 在其他的文件中要使用它,可以用extern int ER;此时的ER变量和前面保持一致;如果在其他文件中去掉extern,如:int ER;此ER为局部变量和前面完全不同。   <o:p></o:p>

register: 寄存器变量,寄存器变量比内存变读取速度更快,但是寄存器变量资源有限,不能存放太多变量和太大变量比如:double型.<o:p></o:p>

static:静态变量,从第一次调用的时候初始化一次,然后在域没消失前,数据不会初始化;数据会常驻内存,一直到程序结束。<o:p></o:p>

<o:p></o:p>

const:把变量声明转换成常量的声明;所谓转换成常量并不是真的变成了常量,只是把变量设置成只读变量;<o:p></o:p>

例如:const int num;//把num转换成只读变量<o:p></o:p>

  num=12;//不允许写入,只能读;即会错误提示<o:p></o:p>

  const int num=12;//允许在定义阶段初始化。<o:p></o:p>

<o:p></o:p>

sizeof:返回一个类型或变量的字节数;例如:char a[]="asdfs";sizeof(a)=5;注意这里的/0不会计算在内;<o:p></o:p>

volatile:告诉编译器定义的变量是一个常改变的值;使得在编译器优化的时候把变量读入缓存的问题;<o:p></o:p>

volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.<o:p></o:p>

 
 
参数是放在栈中的,超过4个参数就放入内存,可读性就降低。
全局未初始化的不分配内存空间,比如:char str[8000];未初始化,设主程序的大小是8K,编译后还是8K;如果把char str[8000]="hello";则必须分配空间大小是32K,编译后的大小就是8+32=40K;
C中内存分为四个区
栈:用来存放函数的形参和函数内的局部变量。由编译器分配空间,在函数执行完后由编译器自动释放。 
堆:用来存放由动态分配函数(如malloc)分配的空间。是由程序员自己手动分配的,并且必须由程序员使用free释放。如果忘记用free释放,会导致所分配的空间一直占着不放,导致内存泄露。 
全局局:用来存放全局变量和静态变量。存在于程序的整个运行期间,是由编译器分配和释放的。 
文字常量区:例如char c = “123456”;则”123456”为文字常量,存放于文字常量区。也由编译器控制分配和释放。 
程序代码区:用来存放程序的二进制代码。
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
      




附件列表

     

    posted @ 2017-01-16 17:28  suntl  阅读(605)  评论(0编辑  收藏  举报