C/C++关于指针
一些人可能为认为指针就是一个变量的内容就是加一个变量的地址. 这样认为非常对. 而如果说指针的知识只有这么多, 那么你就大错特错了. 指针并不是你想的那样只是一个变量指向另一个变量, 从而可以改变它的值.
我写这个的目的就是因为网络上有关于这些的好文章太少了. 虽然这篇文章是我自己的理解,
有可能会有一些许错误, 但是我感觉我还是有必要来分享我的理解的.
我的这篇文章主要是针对C语言的指针, 所以要学过指针. 明白什么是指针, 二级指针, 数组, 和多维数组与一维数组的关系, 因为下面我对这些知识就不再详细说明了.
操作方法
- 01
指针如果普通的理解就是一个变量(常变量)的内容就是另一个变量的地址. 32位编译器的指针类型其实就是unsigned int而64编译器就是unsigned long int.我在这里用的是VC2013.通过代码 cout<<sizeo(void*); 或printf("%p", sizeof(void*)); 我们知道了, 这个编译器的指针占4字节, 所以是32位编译器, 指针是unsigned int 类型的.
- 02
int i = 10; int *p = &i; void *v = p;p = v; //错误, 任何类型指针可以赋给void型指针, 但void型指针不能赋给其他任何指针.为什么要void型指针?自我感觉是为了完成一些库函数的任务比如说free的参数, malloc的返回值等等.*v; 错误, 因为它是void型指针, 编译器不知道sizeof(void)是多少. VC2013中的错误信息是: error C2100: 非法的间接寻址同样, v也不能作加减运算如:v += 1, 因为我们不知道void型的大小.
- 03
不在void型指针上作文章了, 现在开始说说指针的类型. 指针的类型并不是你想的一级指针, 二级指针等. 它其实是有 无数种类型的. 光是几级几级的指针就可以是无数种. 但还有别的. 指针共分为以下几种大类型: 1. 普通指针 typename *name(一级指针, 二级指针等) 2. 数组指针 typenamename [length](一维数组指针和以与变通指针通用) 3. 指向数组的指针 typename (*name )[length](就是在数组指针前面加个&, 它的值与不加&一样) 它们之间也是有关系的, 3归属于2, 2又归属于1. 为什么这样说?我们知道, 一个一维数组的数组名可以赋给一个指针, 如: int a[10]; int *p = a; 这样是正确的, 但这样就违背了我上面说的分类了. 因为a算是数组指针, p算是普通指针. 其实p的类型你可以看作是 int []类型的. 或者说a的类型可以看作是int *类型的. 为什么那个类型要加黑并斜体? 因为, []中间我没写数字, 那么这是为什么? 不写, 说明它中间可以是任何数字. 也就是说, 可以是10, 所以int *p = a是成立的. 同样: int a[10][20]; int (*b)[20] = a; 这样亦是成立的.因为*b可以看作是[]这样int (*b)[20]就可以看作是int b[][20]而[]中间可以是任何数字, 所以这样作成立. 那么, 为什么要加那个括号? 因为, []的优先级和*不一样[]比*要高.如果不加括号, 它的意思是: 而我们是想让它 指向 一个元素为10的一维数组, 且这个一维数组的各个元素也是一个长度为20的一维数组 为什么这里我没有用建立?因为它在上一行已经建立过了, 这里 仅仅是指向它
- 04
我们知道, 多维数组并不存在, 而是一维数组又套着一维数组.二维数组也是二级指针的一种, 三维数组也是三级指针的一种, 但它们又不是. 因为赋值的时候会报错. 如: int i; int *p = &i; int **q = &p; int a[10][20] = {{0}}; q = a; //错误.因为a是int[10][20]型的, 而q是int **型的(也是int [1][1]) 那么为什么要作这个区别?原因很简单, 因为数组要取值. # define A 10 int a[A] = {0}; int i = rand() % A; a[i] == *(a+i); //这一句永远是真. 也就是说a[i]永远等价于*(a+i)那么二维数组呢? int a[10][20]; a[10]得到的是一个指针, 它不类型不是int *, 而是int[20].为什么要作这个区分?下面的代码看过后, 相信你就知道了. a[i][j]; *( *(a+i) + i*j) ); 这句很乱, 我们先把这些变量的类型说下, a: int[10][20], i: int, j: int. 也就是说a是一个二级指针.它加一个*是指针, 再加一个才是有用的数据.所以 *(a+i): int [20] (指向数组的指针) 我们把这个表达式执行后的结果用X表示 那么: *( X + i*j)的类型就是int型. 类型X是int [20], 也就是个一级指针 (上一页的斜黑体字)指针再加个*, 那就是它所指向的数据.
- 05
上一页的一些知识可能会没看懂, 这里我再说些别的. 这个知识点用我的话说就是: 每一个表达式都有值为什么?因为:1 + 2+ 3这个表达式再简单不过了, 但其实它和上一页的内容就有关系. 1 + 2+ 3, 因为都是+号, 所以优先级一样. 而+的结合顺序是从左到右, 所以先执行1+2, 1+2的结果是3, 所以剩下部分就是 3 + 3其中第一个3是1+2的结果, 后面的3是我们代码中的3. 3+3的结果是6, 所以这个语句的结果就是6. 而void, 它也是一种类型, 自我感觉它也是占空间的, 应该是1字节, 内容为0.也就是NULL ()
- 06
然后这里再说说关于字符串(字符指针) 为什么我要把这个内容放到这个文章, 并且是最后?因为字符串和 我说过的这些知识都有关系.. 为什么?从很多地方都可以得到证明: char *= "abcdefghi"; int printf( const char*, ...); 还有一点, 学过C++的都可以试试: char ch = 'a'; char a[10] = "bcdefghij"; char *p = &a; cout<<p<<endl; 输出的不是ch的地址, 而是abcdefghij . 这个其实用C语言也可以表现出来, 但是没有C++更好理解, 因为C++的cout类输出不用写类型, 而C语言要写输出类型. 因为你要输出ch的地址, 用的肯定是%p, 所以它就直接用unsigned int的解码方式去输出收到的参数. 所以就不会输出字符串了. 如果要用C语言实现, 把最后一句改成: printf(" %s", p); %s, 而不是 %p 别的我就不再一一说出了.那么字符串和字符指针, 字符又有什么关于呢? 就拿char *ch = "abcdefghi"; 这个语句来说吧.ch其实是指向a的. 也就是说*ch == 'a'; 而字符串 一定是 连续的, 所以a的后面就是b, *(ch + 1) == 'b', 或者是ch[1] == 'b' 如果用代码实现字符串的输出, 我认为大家就会完全明白了: int myprintf (const char*c){ int i; for (i=0; c[i] != '\0'; i++) { putchar (c[i]); } return i + 1;} 其中返回值要不要都可以. 现在再回来char *ch = "abcdefghi"这个定义中来. 这个定义执行后, 程序中是多出了14个字节的空间. 其中4字节保存ch, ch中保存的就是a的地址. 剩下10字节, 9字节保存有效字符a-i, 还有一字节保存\0. 而保存字符串的那10字节的空间是 只读的这个关于的字面量池. 有兴趣的可以看一看. 如果没有兴趣看的, 就记住:char *name = "xxxx";的定义中, 除了name指向的字符是不可改变的, 因为: char *p = "abc"; char *q = "abc; p和q是 相等的这是为了节省空间. 只保存一个备份. 但是你如果改变了p指向变量的值, 编译器是不会报错的, 运行的时候才会出错, 所以我们要么不这样定义, 要么在前面加上const修饰. 当然, 请一定要加上const. 因为编译器是有设置的, 如果工程设置中设置了只留一个备份, 那么它就是只读的, 如果是一个指针分配一个字符串, 那么这样就是可读可写的. 但这样移植性差, 大多编译器的初始设置都是留一个备份的.