数组
首先来看看数组。这里,我把数组分成两类,一类是像(int,double,结构体等的)普通数组。一类是像(字符数组)这样另外的数组。
“普通数组”
int a[] = {1,2}; //未明确指定个数,可以通过后面给出的值的个数来默认。也就是说a[2]是乱码。
int b[100] = {1,2}; //指定100个,后面给的值是前两个,其余的98个赋值为0。
int c[200] = { 0 }; //整个数组全部赋值为0
cout << sizeof(c); //猜猜这句话的输出是多少。是800!也就是说c代表了200个int
//好,下面是重点,我为什么说数组是一种类型呢?
//因为我们可以把这句int c[200] = { 0 };将int [200]看成是一种类型,而c是它定义的变量。
//那么上面的那个800就很好理解了。
cout << c; //我测试了,输出值是一个地址。(说明c本身是一个指针。)
//这也是为什么数组作为形参时可以“退化成”一个地址。
//并且这个地址是第一个元素的地址。
//那么c+1的步长为4
//&c+1的步长为200 * 4 是不是就很好理解了。
//下面是一层抽象化
//定义一个数组数据类型
typedef int (ARR_TYPE)[5];//(关于typedef自己搜一下)
//声明一个int [5] 的数组
ARR_TYPE myArray;
for(int i = 0;i<5;i++)
{
myArray[i] = 100+i;
}
//下一个问题:
//定义数组指针,关于这句话的解释,首先,它是一个指针,其次,它指向一个数组。
ARR_TYPE *p_arr; //ARR_TYPE是前面定义的数组数据类型
p_arr = &myArray; //这是取数组名的地址,也即是第一个元素的地址的地址
for(int i = 0;i<5;i++)
{
printf("%d ", *(*p_arr+i)); //这里的解引用,*p_arr取到第一个元素的地址。
}
//直接定义一个数组指针数据类型
typedef int (* P_ARR_TYPE)[5];
P_ARR_TYPE p_arr_2 = &myArray;
for(int i = 0;i<5;i++)
{
printf("%d ", *(*p_arr_2+i));
}
//还可以直接定义数组指针变量
int (*my_p_arr)[5] = &myArray;
printf("%d\n",*(*my_p_arr+2));
//下一个问题:二维数组
int arr[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
int brr[][3] = {1,2,3,4,5,6,7,8,9};
//这是两种常见的定义方法,需要特别指出的是二维数组的列数是必须要指定的。
//void printArray(int (*myArray)[3],int row,int col)
//关于这个参数的书写,这两种都可以,其中共性就是列数必须要指明。
void printArray(int myArray[][3], int row, int col)
{
for(int i = 0;i<row;i++)
{
for(int j = 0;j<col;j++)
{
//printf("%d ",*(*(myArray+i)+j));
printf("%d ",myArray[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
printArray(arr,3,3);
return 0;
}
关于“普通数组”,还要特别讲解一下关于结构体的数组。
首先是结构体的简单知识回顾。
struct Person{ //定义一个结构体数据类型。
char name[64];
int age;
};
struct Person p1 = {"小白",20}; //struct Person表示一个数据类型
struct Person2{ //定义一个结构体数据类型,并顺便定义一个变量。
char name[64];
int age;
}p2;
struct Person3{ //定义数据类型和变量,并顺便初始化。
char name[64];
int age;
}p3={"小红",18};
typedef struct Person4{ //定义别名。
char name[64];
int age;
}Person4;
//结构体的偏移量问题
#include <stddef.h> //offsetof
struct A{
char a1;
int a2;
};
struct C{
int a;
double b;
};
struct B {
char a;
int b;
struct C c;
};
int main()
{
struct B b = {'a',10,30,3.056};
cout << sizeof(struct C) << endl; //输出是16
int off1 = offsetof(struct B,c); //输出是8
int off2 = offsetof(struct C,b); //8
printf("off1 = %d\n",off1);
printf("off2 = %d\n",off2);
//下面的利用偏移量的方法都可以正确的输出相应的值
printf("c.b = %f\n", *(double *)(((char*)&b+off1)+off2));
printf("c.b = %f\n",((struct C *)((char*)&b+off1))->b);
struct A a = {'h',20};
printf("A a : a2 %d\n",offsetof(struct A,a2));
printf("a2= %d\n",a.a2);
printf("a2 = %d\n", *(int *)((char *)&a+offsetof(struct A,a2)));
printf("a2 = %d\n",*((int *)&a+1));
return 0;
}
//这是关于结构体赋值的问题。
//结构体可以直接赋值,比如p2=p1; p1和p2都是结构体
//浅拷贝和深拷贝
//浅拷贝就是直接数据的拷贝
//深拷贝则是对地址的操作。
//来了,结构体指针
Person4 p4 = {"小黑",22};
Person4 *p = &p4; //表示p存放的是p4的地址。那么p4的地址又是什么?
cout << sizeof(Person4) << endl; //输出68
Person4 p4 = {"小黑",22};
cout << sizeof(p4)<<endl; //输出68
cout << &p4 << endl; //这个输出和下面的输出是一样的
cout << &p4.name << endl; //也就是说p4的地址是p4的第一个元素的地址。
//这里注意将它和普通整型数组相区分。这个结构体数据类型可以类比于int
printf("p->name:%s,p->age%d\n",p->name,p->age);
//在堆上分配结构体变量的空间
Person4 *q = (Person4 *)malloc(sizeof(struct Person4));
strcpy_s(q->name,"小紫");
q->age = 19;
printf("q->name:%s , q->age:%d\n",q->name,q->age);
free(q);
q = NULL;
//下一个问题,结构体数组
//在栈区分配内存
struct Person persons[] = {
{"小白",20},
{"小白2",22},
{"小白3",23},
}; //这里是结构体数组,这里把struct Person []看成是一种数据类型,persons是变量。
cout << sizeof(persons) << endl; //204
cout << sizeof(struct Person) << endl; //68
cout << persons << endl; //006FF6F8 //也就是数组名存放的是第一个元素的地址。
//这里与一个普通的整型数组是一样的。
cout << &persons[0] << endl; //006FF6F8
//下面是在堆区分配内存
struct Person *pArr = (struct Person *) malloc(sizeof(struct Person)*3);
for(int i = 0;i<3;i++)
{
sprintf_s(pArr[i].name,"小白_%d",i+1);
//sprintf()函数和printf()类似, 只是把输出发送到buffer(缓冲区)中.返回值是写入的字符数量.
//并且这个函数可用于多个字符串有大量相似部分,只有一小部分不同的情况。
//比如这里,我们分别将三个姓名初始化为小白_1,小白_2,小白_3.
pArr[i].age = 18+i;
}
for(int i = 0;i<3;i++)
{
printf("%s,%d\n",pArr[i].name,pArr[i].age);
}
free(pArr); //最后记得free
pArr = NULL;
//最后一个问题,关于结构体的位域
struct data2 {
int a : 4;
int : 4; //表示空域,跳过这段空间
int b : 2;
};
struct A {
int a : 5;
int b : 3;
};
int main()
{
char str[100] = "3134324324afsadfsd1fj1s5jf5"; //这个数组的每个元素是字符。
struct A d;
memcpy(&d, str, sizeof(struct A));
printf("d.a = %d\n", d.a); //输出是-13
printf("d.b = %d\n", d.b); //输出是1
//具体介绍一下怎么算的
//我使用的电脑是从低位开始存放数据的。因此,虽然传递了4个字节给d,但是因为
//使用了位域,因此,d实际使用的字节只有1个。‘3’字符的ASCII码为51,化成二进制为00110011
//实际上d.a的二进制为10011,d.b的二进制为001.并且他们是补码。(现在计算机一般都用补码)
//所以结果是-13和1.
printf("sizeof(struct A):%d\n", sizeof(struct A));
return 0;
}
struct data1 {
int a : 7;
int b : 3;//下一个单元开始存放
};
int main()
{
struct example {
unsigned a : 1;
unsigned b : 3;
unsigned c : 4;
}bit, * pBit;
bit.a = 1;
bit.b = 7;
bit.c = 15;
pBit = &bit;
pBit->b &= 3;
pBit->c |= 1;
printf("pBit->b = %d, pBit->c = %d\n", bit.b, bit.c); //结果为3和15
return 0;
}
“另外的数组”
这里指的是字符数组。
上面例子中看到的那个数组char str[100] = "3134324324afsadfsd1fj1s5jf5"; //这个数组的每个元素是字符。
,它和普通的数组不一样。我们知道普通的数组名代表的是数组首元素的地址。而这个数组不是这样的。请看:
int main() {
char ch[100] = { '3','4','5','6','7','8','f' };
cout << ch << endl; //输出是345678f
cout << &ch[0] << endl; //输出是345678f
cout << &ch[1] << endl; //输出是45678f
cout << &ch << endl; //输出是009AFC74(本次运行)
cout << (int *)&ch[0] << endl; //输出是009AFC74,说明这个&ch代表的是第一个元素的地址
//并且用这种方法才可以输出第一个元素的地址
char str[100] = "3134324324afsadfsd1fj1s5jf5"; //这个数组的每个元素是字符。
//通过调试VS发现,其实这个字符串的存储是分割成一个一个字符的。本质上和上面的字符数组一样。
cout << str << endl; //输出一整个字符串
cout << &str[0] << endl; //输出一整个字符串
cout << &str << endl; //输出地址,007FFBEC
cout << (int*)&str[0] <<endl; //输出地址,007FFBEC
cout << str[0] << endl; //输出3
cout << str[99] << endl; //输出是NULL
cout << str[99]+1 << endl; //输出是1 因为NULl的ASCII码为0
return 0;
}
int trimSpace(char* inbuf/*in*/, char* outbuf/*out*/);
int main()
{
char str[] = " abcdefgdddd "; //存放在栈区
//char* str = (char *)" abcdefgdddd ";//会报错,因为指向的内存是不可写的 存放在全局区
char buf[1024] = {0}; //将buf初始化为NULL
cout << buf[0]+1 << endl; //输出1.证明确实是初始化为NULL,NULL的ASCII码为0
int ret = 0;
ret = trimSpace(str,buf); //前面没提到的字符数组的数组名是什么,&str代表的是首元素的地址。
//但是这里对于字符串,数组名也可以直接这么写,按照这个函数调用的意思,数组名也表示首元素地址?
//我只能存疑了。(苦笑)
指针步长
指针,听起来就头疼。不不不。听起来就很奇妙。(给自己积极的心理暗示)
看代码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h> //前三行为常规框架
//指针步长
void test01()
{
char* p1 = NULL; //p1为指针,指向char类型的指针
printf("%d\n", p1); //这里的输出结果是0
printf("%d\n", p1 + 1); //来了来了,关键点,这里的输出是1
int* p2 = NULL; //p2是指向int类型的指针
printf("%d\n", p2); //这里的输出结果为0
printf("%d\n", p2 + 1); //这里的输出结果是4。
//从这两段代码,我们可以知道指针的步长具体是多少了。
//具体看其指向的数据类型占几个字节。
//下面是进一步理解。
char buf[100] = { 0 }; //定义数组并初始化为0
int a = 10011; //定义一个整型变量a,可以看出来它比较大,一个字节是放不下的
memcpy(buf + 1, &a, sizeof(int)); //这句话是说把4个字节的数据给buf[1];
//来来,来一起分析。memcpy函数表示拷贝,
//是将从&a开始的sizeof(int)个字节的数据拷贝到buf+1的位置。buf+1就表示buf[1]。
//那前面定义了,buf[1]只有一个字节(是字符型)。因此,虽然函数拷贝了四个字节,
//但实际上只有一个字节的数值放进去了,那放进去的是哪一个呢?
//(我估计会因大小端存放方式不同而不同。不懂请忽略)
printf("%d\n",buf[1]); //这句话就是输出buf[1]
char* p = buf; //其实数组就是指向字符的指针(这个形式清记住。)
printf("%d\n", *(int*)(p + 1)); //这句话,首先,p+1表示移动到buf[1]位置,因为步长为1,
//然后通过(int *)强制类型转换,转换成int *,这时候就可以去从&buf[1],
//该地址开始的四个字节的数据了。然后通过解引用得出具体数值。OK!
}
指针步长讲完。