指针变量也是个变量,不过保存的是另外一个变量的地址。另外编译器还会记住指针所指向变量的类型,从而在指针运算时根据变量类型采起不一样操做。html
例如,char * a
定义了char 类型的指针变量 a,经过 *a
读取数据时,每次只会读一个字节(char 类型变量的长度)。而int * i
定义了 int 类型的指针变量 i,经过 *i
读取数据时,每次会读两个或四个字节(int 类型变量的长度跟编译器平台有关)。web
#include <stdio.h> int main() { int a = 666; char c = 'a'; int * p1 = &a; // 至关于(int *) p1,表示 p1 是执行 int 类型的指针 char * p2; // 至关于(char *) p2,表示 p2 是执行 char 类型的指针 p2 = &c; // & 符号用于取变量的地址 printf("address of a is %#x, value of a is %d\n", p1, *p1); printf("address of c is %#x, value of c is %c\n", p2, *p2); }
输出:编程
address of a is 0x107900bc, value of a is 666 address of c is 0x107900bb, value of c is a
#include <stdio.h> int main() { float a = 1.2; char * p = (char *)&a; // 这里的 p 指向有符号字符型变量,符号位为1时会打印4个字节 printf("%#x\n", *p); }
输出:数组
0xffffff9a
要解决这个问题,把上面的 char * p = &a;
变成 unsigned char * p = &a;
便可。bash
有时咱们须要用 char * 按照字节大小读取数据,可是非 char 类型的指针当作 char 指针处理时会报错或警告。这时须要强制类型转换:网络
#include <stdio.h> int main() { int a = 0x77777777; char * p = (char *)&a; printf("%#x\n", *p); }
指针操做若有不慎,会常常看到 Segmentation Fault 段错误。这是由于指针指向了非法的内存,例以下面的代码执行的内存地址,操做系统是不容许访问的:svg
#include <stdio.h> int main() { int a = 0x12345678; int * p = &a; p = 0x00000001; printf("address of a is %#x, value of a is %d\n", p1, *p1); }
内存除了用于存放程序运行时的数据外,还有一部份内存用于操做硬件。例如内存的某一段连续空间用于映射显存、I2C、USB 设备等。函数
对于单字节的 char 类型变量不存在这个问题。但多字节的变量,高字节存储在内存的高地址仍是低地址,决定了采用哪一种存储方式。优化
#include <stdio.h> int main() { int a = 0x12345678; unsigned char * p1 = &a; printf("address of a is %#x, value of a is %#x\n", p1, *p1); }
上面代码在 32 位平台上运行时,经过 char 类型的指针只读取第一个字节,若是输出 78 表示是小端存储(低地址存放低位),不然是大端存储。输出:ui
address of a is 0xbb483e84, value of a is 0x78
目前Intel的80x86系列芯片是惟一还在坚持使用小端的芯片,ARM芯片默认采用小端,但能够切换为大端。另外,对于大小端的处理也和编译器的实现有关,在C语言中,默认是小端(但在一些对于单片机的实现中倒是基于大端,好比Keil 51C),Java是平台无关的,默认是大端。在网络上传输数据广泛采用的都是大端。
C 语言的 const 比较弱,很容易绕过去,例如经过指针。const 修饰的变量仍然存储在读写区,而非只读区。
const char * p; // 推荐使用,至关于 const ((char *) p) char const * p; // 不推荐
char * const p; // 推荐使用,至关于 (char *) (const p) char * p const; // 不推荐
const char * const p; // 至关于 const ((char *) (const p))
综合示例:
#include <stdio.h> int main() { char * str1 = "hello\n"; // C 语言中字符串不可修改 char str2 [] = {"hello\n"};// 数组能够修改 //str1[0] = 'a'; // str1 修改会致使 segmentation fault str2[0] = 'a'; printf("%s\n", str2); }
上面代码中,字符串是不可修改的,因此能够用 const 限制,若是有代码则会在编译时报错:
int main() { char * str1 = "hello\n"; // C 语言中字符串不可修改 const char str2 [] = {"hello\n"};// 数组能够修改 str1[0] = 'a'; // str1 修改会致使 segmentation fault str2[0] = 'a'; printf("%s\n", str2); }
编译报错:
/code/main.c: In function ‘main’: /code/main.c:9:2: error: assignment of read-only location ‘str2[0]’ str2[0] = 'a';
#include <stdio.h> int main() { int a = 0x66667777; int b = 0x11111111; int *p = &b; *(p+1) = 0xffffffff; printf("%#x\n", a); }
编译器默认的优化是开启的。但有时候咱们操做的内存是映射到硬件的,此时可能须要关闭优化。
volatile char * p = 0x20; while (*p == 0x20) ...
指针能够指向任意类型的资源,例如 int、char、数组、函数。指定简明易读的别名能够提升代码可读性。
char * name_t; typedef char * name_t; name_t myVar;
指针的加减操做,跟指针指向变量的具体类型有关。指针指向的变量占几个字节,指针每次加减一就是加减几个字节,确保恰好能够指向下一个同类型元素。
#include <stdio.h> int main() { const char *p = {"hello\n"}; int *s = p; printf("%c, %c, %c, %c, %#x\n", *p, *(p+1), *(p+2), *(p+3), *s); }
在数组中,保存的是相同类型的元素。经过下标能够访问到每个元素,不须要咱们在编程的时候关系元素占几个字节。这跟指针的加减运算是同样的。p[0] 等价于 *p,p[1] 等价于 *(p+1),以此类推:
#include <stdio.h> int main() { const char *p = {"hello\n"}; printf("%c, %c, %c, %c\n", *p, p[0], *(p+1), p[1]); }
指针能够进行比较,>= 、<= 、== 、!= 四种。
经常使用的是二维指针,二维以上基本上不用。
当在内存中有多个离散的变量时,为了放在一个变量中统一访问,就须要把这个用做访问入口的统一变量设计为数组,数组中的每一个元素都是指针,执行原始变量。
语法的简单示例:
int 变量int a;
← int 变量的指针int * p = &a;
← int 变量的指针的指针int **p2 = &p;
bash 终端能够在命令后面带参数,编译器会把全部参数汇总到 main 函数的参数中:
#include <stdio.h> int main(int argc, char ** argv) { int i; for (i = 0; i < argc; i++) { printf("argv[%d] is: %s\n", i, argv[i]); } i = 0; while(argv[i] != NULL) { printf("argv[%d] is: %s\n", i, argv[i++]); } return 0; }
# ./build 666 hello world ! argv[0] is: ./build argv[1] is: 666 argv[2] is: hello argv[3] is: world argv[4] is: ! argv[1] is: ./build argv[2] is: 666 argv[3] is: hello argv[4] is: world argv[5] is: !
数组是地址操做的一种形式,使用的时候跟指针几乎同样。经过数组分配的内存空间的特性以下:
int a[10]; // 分配 4*10Byte 的内存,a 是指向这个内存的标签,不可变,不是指针
C 语言只有指针的概念,并无真正意义的数组,因此在用指针操做数组时,须要注意:不要越界。
#include <stdio.h> int main(int argc, char ** argv) { char a[] = {"hello\n"}; char * p = {"hello\n"}; printf("a is: %s\n", a); printf("p is: %s\n", p); //a = "hello"; // a 是标签,数组不可变,不然编译报错 p = "world\n"; // p 是指针,能够变 printf("a is: %s\n", a); printf("p is: %s\n", p); }
关于char、unsigned char 和 signed char 三种类型直接的差异,能够参考:http://bbs.chinaunix.net/thread-889260-1-1.html
内存中的数据空间能够分为两类:
char a[10];
。用 strcpy 复制数据,复制时以 \0 结束,或者用 strncpy 复制。unsigned char b[10]
。用 memcpy 复制数据,复制时须要指定字节个数。int buf[10]; int source[1000]; memcpy(buf, source, 10*sizeof(int));
注意:C 语言中只有字符串常量。由于 C 语言没有字符串变量的概念,若是想修改字符串的值,必须将字符串存储为字符数组。全部字符串都以 \0 结尾。
char a[] = "hello\n"; // C 编译器看到双引号时,自动在末尾加 \0 char b[10] = {'h', 'e', 'l', 'l', 'o', '\n', '\0'}; // 未赋值的元素默认是0 char c[] = {"hello\n"}; // 由于双引号和大括号都用来划分存储空间,可省略大括号 int i[] = {12, 23, 666};
char a[10]; a[0] = 'h'; a[1] = 'e'; ...
字符串是 C 语言中须要特别注意的地方。字符串常量赋值到数组时,实际上会先建立一个数组变量,而后依次把每一个字符拷贝到这个数组中,数组指向的变量跟字符串常量无关,能够修改。但字符串赋值到指针时,指针指向的就是这个字符串常量,此时指针指向的值不可修改。
char a[10] = {"hello"}; // 内存中分配了一个字符串常量空间和一个字符串变量空间,变量 a 指向这个变量空间,能够修改空间中的元素 a[2] = 'w'; // OK char *p = "hello"; // 内存中只有一个字符串常量空间和一个指向该常量的指针变量,指针变量 p 指向常量,不可修改 p[2] = 'w'; // 报错 segmentation fault
C 语言中,数组中的每一个元素能够修改,可是不可直接对数组名进行赋值。若是想再次赋值,只能逐个元素赋值。
int a[] = {2, 5, 6}; a = {3, 5}; // 编译报错,数组名相似函数名,是个常量标签,不可赋值
内存空间逐一赋值操做很常见,因此 C 语言将其封装为字符串拷贝函数。能够在 Linux 下经过 man 3 strcpy
之类的命令查看函数定义。
strcpy 函数碰到 0 就中止拷贝。若是源字符串太长,strcpy 可能致使内存泄漏,通常不用。函数原型以下:
char *strcpy(char *dest, const char *src);
char a[] = "666"; strcpy(a, "hello world");
strncpy 函数能够限制拷贝的数量,防止发生越界。
char *strcpy(char *dest, const char *src, size_t n);
数组中存在指针,构成指针数组。指针数组就是二级指针。
int *a[10]; // 开辟 10 个空间存放数组 a,a 中放 (int *) 类型的指针 int **a; // ((int *) *) a
C 语言中,一维数组的数组名变量中放的就是数组首元素的地址,能够直接赋值给指针,并用这个指针访问数组中的元素。但二维数组跟二维指针没有任何关系。
下面例子会报错,p2 指向指针数组,但 b 指向两个连续的内存块,每块内存由 5 个 int 类型变量组成
#include <stdio.h> int main() { int a[10]; // a 是数组标签,表示一块由 10 个 int 元素组成的空间 int b[2][5]; // b 是数组标签,表示两块空间,各由 5 个 int 元素组成 int *p1 = a; int **p2 = b; // 这一行会报错 int *p4 [5] = b; // 这一行会报错,这里 p4 是数组,其中的每个元素都是 int 类型的指针 int (*p3)[5] = b; // 正常编译,这里 p3 是指针,指向一块由 5 个 int 元素组成的空间 printf("%d\n", a[5]); printf("%d\n", b[1][1]); printf("%d\n", p3[1][1]); }
对于三维数组 int a[2][3][4];
,能够用指针表示:
int (*p) [3][4];