C指针和位运算

文章目录

  • 跟据符号顺序依次判断类型
  • 指针的类型和指针指向的类型
  • sizeof
  • 指针的值
  • 指针算数运算
  • 运算符
  • 指针表达式
  • 数组和指针
  • 指针和函数
  • 指针类型转换
  • 转换的安全问题
    • 浮点型强制类型转换
  • 位运算
    • n&(n-1)
    • 加减法

跟据符号顺序依次判断类型

int p; //普通整型

int *p;//带*说明p是一个指针,int说明p是指向整型的指针

int p[3];//带[]说明p是一个数组,int说明数组里存放的是整型元素

int *p[3];//[]说明是数组,*说明数组里的元素是指针,int说明这些指针元素指向整型。所以p是一个储存了整型的指针所组成的数组。

int p(int);//p有()说明是个哈桑农户,函数有一个整型的参数,该函数返回的是整型

int (*p)(int);//*说明p是指针,()说明*p是指向函数的指针


指针的类型和指针指向的类型

int *p;
指针的类型是整型指针;
指针所指向的类型是整型;
int *pi;
*pi=5;
这样是错误的,因为整型指针只是声明但是没有实际的地址,赋值的话只有对象但是没有地址。
int a[3];
int *p,*q;
p=a;
q=&a[2];
这两种都是可以的,(q-p)/sizeof(int)
const char *c = "hello";//常量代码区
char a[] = "hello";//数据区或者栈区
int *ptr;
ptr = (int*)0x8000;
这种随意直接分配地址的方式是十分危险的,因为可能并不知道这些地址指向什么地方

sizeof

char *a = "0123456789";
sizeof(a)
a是一个字符指针,指针的大小是定值,就是4字节
char a[] = "0123456789";
sizeof(a)
a是一个字符数组,数组最初未确定大小,填充后一个字符占一个字节,再加上隐含的\0,所以大小是11
char a[100]="0132465789"
sizeof(a)
a是一个字符数组,由于分配好了大小100,所以这个字符数组就是大小100
int a[100];
sizeof(a)
a是一个整型数组,一个整型的大小是4字节,所以最后整体的大小应该是400
char a[]="a\n"
sizeof(a)
由于\n算作是一个字节,所以最后加上\0大小是三
char var[10];
int test(char var[])
{
	return sizeof(var);
}
本来var代表这个数组,其sizeof10,但是由于作为函数参数传入之后,就退化为指针了,指针的大小都是4

指针的值

指针的值是一个地址,在32位程序中是一个32位的整数。指针指向的区域就是,从这个地址开始,然后长度位sizeof的区域,指针就是这个区域的首地址。32位是4个字节大小。指针大小与类型无关,始终是4个字节。变量的大小才会有区别。

指针算数运算

char a[20];
int *ptr = (int *)a;
ptr++;

a是一个字符数组变量,使用(int *)类型转换成了一个整型的指针,方便后面进行运算,因为整型的才能做算数运算;但这里即使不转也可以直接用。
ptr加1,在32位中实际是加4,所以ptr指向的地址由原来的a的低地址向高增加了四个字节,本来ptr是指向数组a的开始的四个字节,此时指向()
int array[20] = {0};//注意初始化的格式,后面要么不加初值,要么也是{0}这样加大括号的形式
int *ptr = array;
for(i=0;i<20;i++)
{
	(*ptr)++;
	ptr++;
}

运算符

&是取地址,取出来的是一个指针
*是取出这个指针所对应的内容

int a = 12;//a是个整型
int b; //b是个整型
int *p ;//p是个指向整型的指针
int **ptr;

p =&a;//把a的地址取出来给了p,从此p就指向a了

*p = 24;//p这个指针指向的内容变成24,也就是a=24

ptr = &p;//p这个指针的地址是ptr,所以ptr这个二级指针指向的是p这个一级指针

*ptr = &b;//现在让这个ptr二级指针指向的内容变成b这个内容的一级指针了

**ptr=34;//*ptr待变ptr指向的一级指针,**ptr代表这个一级指针指向的内容,也就是b,所以b=24了

指针表达式

表达式的结果是指针,就是指针表达式

int a,b;
int array[10];
int *pa;

pa = &a;//a的地址为pa,pa指向a
int **ptr = &pa;//pa这个指针的地址为ptr这个二级指针
*ptr = &b;//这两边都是指针表达式

数组和指针

int a;//一个整型数
int *a;//一个指向整型数的指针
int **a;//一个指向整型数指针的指针
int a[10]//一个整型数组
int *a[10]//一个存放着指向整型数的指针的数组
int (*a)[10]//一个指向10个元素的整型数组的指针
int (*a)(int)//一个返回值为整型,参数为整型的函数的指针
int (*a[10])(int)//
int a[] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d",*(a+1),*(ptr-1));

a首先是一个数组的指针,其次这个指针里还存放这个数组的大小
&a得到的是这个数组的地址,这个地址直接加1的结果就相当于加上了这个数组的大小。这个时候遍指向了第六个不存在的元素。这个元素的指针减1就是第五个元素的指针,*将这个值取出来
而a这个指针直接加1的话就是直接下一个元素

所以:地址加1就是加了这个数组的大小,指针加1就是下一个元素
int *p[n];//指针数组,每个元素均为指向整型数据的指针
int (*p)[n];//p这个指针指向了有n个整型的数组,p为这个整型数组的指针
不要这样赋值 *ptr[]=5,因为没有地址,必须先让ptr[5]=a这样存有地址才行
int *(ptr[])和上面相同
int v[2][3]={{1,2},{3,4}};
int (*a)[2]=v;
cout<<**a;//1
cout<<**(a+1)//3
cout<<*(*a+1);//2
cout<<*(a[0]+1)//2
cout<<*(a[1])//3

这个数组的指针是v,而a这个数组的指针指向了v这个指针,所以a是一个二级指针,即指针的指针a=&v,按照上面的理论,a+1就是地址加1,直接加上了这个数组的大小.*a=v是指针加1相当于下一个元素
里面的a+1相当于加了一个数组大小,所以到了下一个数组处
*a+1相当于指针向后移动1个,指向下一个元素

char * a[]={"hello","the","world"}
char **pa = a;
pa++;
cout<<*pa;
a是一个二级指针,指向了这个数组的指针的指针。pa也是一个二级指针,接收了a的值。pa是这个这个数组指针的地址,地址直接加1的结果就是加上了当前额sizeof,所以直接到了第二个的开头,打印*pa输出的是the
char str1[] = "abc";//字符数组,str1存的是字符数组的首地址
对其求sizeof是数组的长度,专门有个‘数组的指针’的机制,但是当作为函数参数等情况发生后就变为‘普通的指针’了
const char* str3 = "abc";//str3是一个字符指针,str3指向该常量储存区的首地址的指针
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value = *array;
value = *(array+3);
value = *(array+4);

数组名代表的是数组,也可以看作是指向第一个元素的指针
char s[3]={};
s就是这个数组的指针,s的值就是第一个元素的首地址,这个时候代表的是整个的数组,求sizeof也求的是整个的数组的长度。s的类型是char *的类型,指向的是char数组。
char *str[3] = {};
str这里是一个二级指针,类型是char**,指向的是char*,str是一个放有三个指针的数组,元素都是指针,每个指针都指向一个字符串,所有赋值的时候{}里面就代表这些指针分别指向的字符串。注意字符串算是一种字符常量,内容不能改变,有const属性。
*str是一个指针,它的类型是char*,它所指向的类型是char,所指向的地址是字符串第一个字符的地址。
*(str+1)也是一个指针,指向的是数组的一号元素,指向的类型是char

TYPE array[n];
对于数组名array有两层含义,
1.它就代表这个数组。类型是TYPE[N]2.它也看成一个常量指针,指针的类型是TYPE*,指针指向的类型是TYPE,指向的内容是数组的0号元素,但是注意它和0号单元的地址是不一样的,是在常量区,因为始终会指向这个0号元素这个位置,但是这个位置的值可以通过0号元素的指针去进行修改。所以对这个常量指针求sizeof的结果得到的是这个数组的长度。
*array直接就代表的是这个数组0号元素的值了,而求后面的元素的值要先对指针进行加减,如*(array+1)代表的是第一号元素,这时对这个指针求sizeof得到的就直接是本身指针的大小了,也就是4
struct my
{
int a;
int b;
}
struct my ss = {10,20};
struct my* ptr = &ss;
int *ptrs = (int*)&ss;
ptr的类型为struct my *,对象ss的地址赋给这个指针,以后ptr就是ss的指针,即*ptr=ss
&ss的类型经过强制类型转换后变为int *型,然后给到ptrs,ptrs和ptr的类型是不同的

通过ss访问成员:
ss.a
ss.b
通过ptr访问成员:(对于指针类型的使用->访问,迭代器也是,链表的节点指针也是)
ptr->a
pts->b
通过ptrs访问成员:(非常不推荐,因为结构体成员之间可能存在空隙,而数组可以这样做,因为数组是顺序储存的)
*ptrs
*(ptrs+1)
int array[3] = {};
int *p = array;
array是这个数组的指针,int *类型的p直接等于array,注意这里不用再对array取地址了
*p直接就是0号元素
*(p+1)1号元素
		char p1[] = "123";
		char p2[] = "123";
		const char *p3 = "test";//这个const不加编译器会报错的,因为这样定义的就是一个常量,常量在常量区,只有一份
		const char *p4 = "test";
		bool b1 = p1 == p2;
		bool b2 = p3 == p4;
		cout <<b1 << endl;//答案为0,因为每个地址下都有自己的123
		cout << b2 << endl;//答案为1,放在的是常量区,只有一份备份

指针和函数

int func1(char *s)
{
	int num = 0;
	for(int i = 0;;)
	{
		num+=*s;
		s++;
	}
	return num;
}
int a;
char str[] = "asd";
a = fun(str);

str数组名代表这个数组,同时也是这个数组的0号元素的指针,通过函数进行传入统计ASCII码值

指针类型转换

void fun(char *s)
{
char c;
c = *(s+3);
*(s+3)=*(s);
*s = c;
}
int a = 125,b;
fun((char *)&a);

&a取地址之后,类型是int *,要想作为参数还需要转换成char *,进行强制类型转换。int类型是四个字节,char类型是一个字节,fun把这个int的四个字节首位进行颠倒
int a = 123,b;
int *ptr = &a;
char *str;
b = (int)ptr;
str = (char*)b;

ptr是a的指针,用强制类型转换把a的地址取出来了然后作为整数给了b,然后又把这个整数转换为char*的类型赋给str,现在str的类型是char*,指向的是char,地址就是b的值

转换的安全问题

ptr1 = (TYPE *)ptr2;
如果sizeof(ptr2)大于sizeof(ptr1),那么使用ptr1来访问ptr2所指向的储存区是安全的。但是如果小于了,会造成不安全的情况。
char s = 'a';
int *ptr;
ptr = (int*)&s;
*ptr = 1298;

ptr是int*的指针,指向的是int,指向的地址就是s的首地址。但是s只占一个字节,int4个字节,*ptr=1298不仅改变了s对应的字节内容,还把s地址后面的三个字节的未知给修改了,造成了不安全

浮点型强制类型转换

float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << ( (int)a == (int&)a ) << endl; 
0
这和浮点数存储有关系:
float 1.0在内存中存储为:
符号位  阶(指数)   尾数
0       01111111   00000000000000000000000

关于浮点数存储可以参见有关标准。

转化为int&后,数据就变为00111111100000000000000000000000 = 1065353216
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << ( (int)b == (int&)b ) << endl; 
1

位运算

n&(n-1)

1.	n&(n-1)消除二进制下最后出现1的位置,其余保持不变

(1)任意给定一个32位无符号整数n,求n的二进制表示中1的个数
	Int func(int x)
{
Int count = 0;
While(x)
{
Count++;x =x&(x-1);
}
}
(2)判断一个数是否为2的n次方
n&(n-1)==0?

加减法

	//1.两种加法运算,运行时间:3ms,占用内存:468k
	int Add(int num1, int num2)
	{
		int sum, carry;
		while (num2 != 0)
		{
			//sum是和,而carry是进位,sum表示计算的①,carry表示计算的②,不断地进行①与②,直到②为0
			sum = num1^num2;
			carry = (num1&num2) << 1;
			num1 = sum;
			num2 = carry;
		}
		return num1;
	}

	int Add2(int num1, int num2)
	{
		if (num2 == 0)
			return num1;
		int sum = num1^num2;
		int carry = (num1&num2) << 1;
		Add2(sum, carry);
	}

	//2.一种减法运算
	int Negtive(int num1, int num2)
	{
		//减法即num1+(-num2),注意计算机用补码存的,所以-num2 = ~(num2 - 1) = ~num2 + 1 = Add(~num2,1)
		return Add(num1, Add(~num2, 1));
	}

你可能感兴趣的:(C++)