1. 概念
1.1 分类
根据数据类型的不同:
- 数值数值
- 字符数组/字符串
- 指针数组
- 结构数组
1.2 概念
C语言数组在存储时,是行优先
多维数组下,如二维数组,可以看成是嵌套:一个数组可以作为外部数组的一个元素
C语言数组是静态的,不能插入或删除元素(数组一旦被定义,占用的内存空间就是不可改变的,但是可以修改和读取数值),如果要改变一个数组,就要再造一个数组
1.3 数组类型
注意:数组也是有类型的!!!
int a[10]; // 数组a的类型是int [10]
1.4 数组与内存
数组内存时连续的,(连续的内存为指针操作和内存处理提供了方便),数组可以作为 缓存 使用,(临时存储数据的一块内存)
1.4.1 数组越界与溢出
数组下标越界不会产生编译错误,只会在运行过程中产生错误
int a[3]={1,2,3}; // printf a[3],如a[4,5,6]...以后不知道是什么值
// 对于int a[3]={1,2,3,4,5};但是高级编译器会报错,或者
// 对于下面这种 编译器也不会报错
int a[3];
for (int i=0;i<5;i++)
{
a[i]=i;
}
// 这里sizeof(a)==sizeof(int)*3
负数索引
尽量不要使用负数索引,负数索引 指的是arr[0]的内存空间中的前一个元素
越界与内存
数组越界赋值,有可能修改其他内存空间中其他数据的值
int main()
{
// 下面的变量 可能在内存空间上是连续的
int var1=10;
int a[3];
int var2=30;
for (int i=-1;i<=4;i++)
{
a[i]=i;
}
for (int i=-1;i<=4;i++)
{
printf("%d\n",a[i]);
}
printf("%d\n",var1); // 可能变成了-1
printf("%d\n",var2); // 可能变成了 4
}
1.5 变长数组
变长数组仍然是静态数据,一旦定义之后都是不可变的
array[length]
scanf("%d",&length);array[length]
1.6 静态分配与动态分配
#define MaxSize 10
typedef struct
{
int data[MaxSize]; // 静态分配
int length;
}SqList;
1.6.1 动态分配与malloc
new是C++中的关键字
malloc 返回的是被 动态分配 内存的首地址
malloc 可以用来返回数组指针、结构指针等
#include <iostream>
#include <cstdlib>
#define MAX 3
using namespace std;
int main()
{
double *ptd=(double *)malloc(MAX*sizeof(double));
if (ptd==NULL)
{
cout<<"ERROR: memory created wrong!";
exit(EXIT_FAILURE);
}
return 0;
}
例子2
#include <stdlib.h>
#define InitSize 10
typedef struct
{
int *data; //动态分配
int MaxSize;
int length;
}SeqList;
void InitList(SeqList *L)
{
L->data=(int*)malloc(InitSize*sizeof(int));
L->length=0;
L->MaxSize=InitSize;
}
void IncreaseSize(SeqList *L,int len)
{
printf("%p\n",L->data); //00000000006916A0
int *p=L->data; // 这是为了防止内存泄漏
printf("%p\n",p); //00000000006916A0
L->data=(int*)malloc((L->MaxSize+len)*sizeof(int)); //0000000000691700 ,就把L->data当做一个指针就行了
printf("%p\n",L->data);
for (int i=0;i<L->length;i++)
{
L->data[i]=p[i];
}
L->MaxSize+=len;
free(p); // 这是为了防止内存泄漏
}
int main()
{
SeqList L;
InitList(&L);
// for (int i=0;i<InitSize;i++)
// {
// printf("%d\t",L.data[i]);
// }
// printf("\n%d\n",L.length);
IncreaseSize(&L,5);
return 0;
}
1.6.2 malloc 与二维数组 – 还没看!!!
https://blog.csdn.net/fengxinlinux/article/details/51541003
1.6.3 calloc/realloc
void * calloc(size_t n,size_t size)
在堆区分配n*size 字节的连续空间
成功分配返回内存地址,失败则返回NULL
n: 单元个数(相当于有多少个单位),size: 单位字节数(每个单元有多少个字节)
void * realloc(void *ptr,size_t size)
对ptr指向的内存重新分配size大小的空间,size可大可小
成功分配返回内存地址,失败则返回NULL
realloc 之后id不变
free(p ) 并不改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,将p=NULL
#include <iostream>
using namespace std;
#define N 5
#define N1 7
#define N2 3
int main()
{
int *ip;
int *large_ip;
int *small_ip;
if ((ip=(int *)malloc(N*sizeof(int)))==NULL)
{
cout<<"malloc ERROR";
exit(1);
}
for (int i=0;i<N;i++)
{
ip[i]=i;
cout<<ip[i]<<endl;
}
// cout<<ip<<endl; // 0x621f50
// (int *)realloc(ip,N1*sizeof(int));
// cout<<ip<<endl; // 0x621f50
if ((large_ip=(int *)realloc(ip,N1*sizeof(int)))==NULL)
{
cout<<"realloc large ERROR";
exit(1);
}
// 两个指向同一块内存
cout<<ip<<endl; // 0x661f50
cout<<large_ip<<endl; // 0x661f50
for (int i=N;i<N1;i++)
{
large_ip[i]=i;
}
for (int i=0;i<N1;i++)
{
cout<<large_ip[i]<<" "<<ip[i]<<endl;
}
if ((small_ip=(int *)realloc(large_ip,N2*sizeof(int)))==NULL)
{
cout<<"realloc small ERROR";
exit(1);
}
// 三个指向同一块内存
cout<<ip<<endl; // 0x661f50
cout<<large_ip<<endl; // 0x661f50
cout<<small_ip<<endl; // 0x661f50
// 缩小只会在有效范围内数值正常,越界会产生随机数
for (int i=0;i<N2;i++) // N 或者N1都是错的
{
cout<<small_ip[i]<<" "<<ip[i]<<endl;
}
free(small_ip); // 只用释放small_ip 其他两个被系统回收
small_ip=NULL;
return 0;
}
1.7 数组与指针 – 见指针
https://blog.csdn.net/L_fengzifei/article/details/126411708
2. 数值数组
创建数组:赋值个数小于开辟的内存空间,且剩余的元素会被初始化为0,
对于整型,初始化为0
对于浮点型,初始化为0.0
对于字符数组,字符数组初始化为\0
int a[10]={1,2,3};
// 只能以循环的方式进行输出
for (int i=0;i<10;i++){
printf("%d\n",a[i]);
}
// 方法2
int a[]={1,2,3,4}; // 自动计算需要开辟的内存空间
// 比较常用的写法
int a[10]={0};
数组越界不会被编译器检测出来错误
下面还有一个越界的例子 “2.1 数值数组与sizeof”
int a3[3];
for (int i=0;i<3;i++)
{
a3[i]=i;
}
for (int i=0;i<3;i++)
{
printf("%d\t",a3[i]); // 0,1,2
}
printf("\n");
// 越界不会报错!!!
// 但返回的是随机值
for (int i=0;i<10;i++)
{
printf("%d\t",a3[i]); // 0,1,2,3,3... 随机数
}
结构体之间可以进行赋值操作
数组之间不可以进行赋值操作
struct1=struct2
// arr1=arr2 // 这种不行
2.1 数值数组与sizeof
越界也会按照开辟的内存,计算sizeof()
// 完全不赋值
int a[10];
int* p=a;
printf("%d\n",sizeof(a)); //40
printf("%d\n",sizeof(p)); //8
// 部分赋值
int a2[10]={1,2,3};
int* p2=a2;
printf("%d\n",sizeof(a2)); //40 , 对应的是开辟的数组
printf("%d\n",sizeof(p2)); //8
// 对于越界赋值,编译器不会出错
// 但是后面访问的时候,并不会按越界赋值的数值显示,显示的依然是随机数
int a3[3];
for (int i=0;i<5;i++)
{
a3[i]=i;
}
int* p3=a3;
printf("%d\n",sizeof(a3)); // 12
printf("%d\n",sizeof(p3)); // 8
/* == 例子2 == */
// 动态计算
int a4[]={1,2,3};
printf("%d\n",sizeof(a4)); //12,计算的还是对应的初始化开辟的内存
for (int i=0;i<5;i++)
{
a4[i]=i; // 越界赋值,编译器不报错
}
printf("%d\n",sizeof(a4)); // 12
for (int i=0;i<5;i++)
{
printf("%d\n",a4[i]); // 越界打印不会报错,打印的依然是随机数
}
2.2 一维数组与指针
数组指针:是指向数组的指针
指针数组:数组中存放的是指针
数组名可以认为是做一个指针,指向数组的第0个元素,但是数组名不可变,数组名本身是常量,所以 数组本身是指针这个说法不准确!!!
第0个元素的地址称为数组的首地址
数组指针 指向的是数组中的一个具体元素,而不是整个数组!!!
指向数组的指针(数组指针),与数组名不同,数组名不可以改变,而数组指针可以改变
注意:++
具有inplace操作
(下面的具有 右结合性)
*p++ 等价于*(P++)
,不能使用*arr++
,因为数组名不能改变
*++p 等价于*(++P) 等价于*(p+1)
(*p)++只对数值进行改变
datatype *p=arr
p+1相当于移动了一个datatype类型的字节
int a[3]={1,2,4};
int* p=a;
// 下面三个是等价的
printf("%p\n",p);
printf("%p\n",a);
printf("%p\n",&a[0]);
// printf("",a++); // 这个是错误的,数组名是不能改变的
printf("%d\n",*p++); // 1 ,*p赋值之后,再++ !!!
printf("%d\n",*(p++)); // 1,*p赋值之后,再++ !!!
printf("%d\n",*p+1); // 2 等价于(*p)+1
printf("%d\n",*(p+1)); // 2
printf("%d\n",*(++p)); //2
printf("%d\n",*++p); //2
int main()
{
int a[3]={1,4,5};
int *p=a;
// printf("%d\n",*p++);
// printf("%d\n",*p);
printf("%d\n",(*p)++);
printf("%d\n",*p);
// printf("%d\n",*(p++));
// printf("%d\n",*p);
for (int i=0;i<3;i++)
{
printf("%d\n",a[i]);
}
return 0;
}
重要
[]
符号具有取内容的作用
a[i]
,*(a+i)
,p[i]
,*(p+i)
这四个是等价的
&a[i]
,(a+i)
,&p[i]
,(p+i)
这四个是等价的
int a[3]={1,2,3};
for (int i=0;i<3;i++)
{
printf("%d,%d,%d,%d\n",a[i],*(a+i),p[i],*(p+i));
printf("%p,%p,%p,%p\n",&a[i],(a+i),&p[i],(p+i));
}
2.2.1 数组名/数组名地址
int main()
{
int a[3]={1,2,3};
printf("%p\n",a); // 0x61fe14 数组首元素地址 a的类型为 int
printf("%p\n",&a); // 0x61fe14 整个数组的起始地址, 类型为int [3]
printf("%p\n",&a[0]); // 0x61fe14
printf("%p\n",a+1); // 0x61fe18 = 0x61fe14 + 0x04 首元素下一个元素的地址
printf("%p\n",&a+1); // 0x61fe20 = 0x61fe14 + 0x0C(12) 跳过了整个数组
// int *p=&a; // 这种是错误的 类型不匹配
return 0;
}
char str[10]="hello"
func(str) 等价于 func(&str)
2.3 memset/memcpy
- memset
将一块内存中的内容全部设置为指定的值
void *memset(void *s,int c,size_t n)
s
为要填充的内存块的首地址,c
为被设置的值(以 补码 进行字节初始化),n
为要设置的字节数(内存块长度),返回一个指向该内存的指针
注意:按字节对内存块进行初始化,所以不能用它将数组初始化为0和-1之外的其他值
即,初始化时并不关心要初始化的数组的类型是什么,均以字节为单位进行初始化
#include <string.h> // #include <cstring>
// 例子1
int a[4];
memset(a,1,sizeof(a))
1 == 0x00000001 以字节为单位初始化,则只能截取低8位0x01
则每个元素初始化成0x01010101 也就是16,843,009
// 例子2
// 下面两个是等价的
memeset(a,-1,sizeof(a)); // -1 在内存中的补码低8位是0x 1111 1111
memeset(a,255,sizeof(a)); // 255 在内存中的补码低8位是 0x 1111 1111
// 例子3
// 清空结构体
typedef struct Stu
{
char name[20];
int age;
}Stu;
Stu stu1;
memset(&stu1,0,sizeof(Stu))
特别注意!!!
memset函数是一个运行时函数,其在运行时才能确定其要设置的字节数,因此不能在全局作用域使用
全局作用域下的变量进行初始化时,只能使用常量表达式或编译时常量(即具有静态存储期的变量),(所以memset不能在全局作用域下进行变量初始化)
下面的例子是错误的!!!
#include <iostream>
#include <cstring>
using namespace std;
int arr[10];
memset(arr,0,sizeof(arr));
int main()
{
return 0;
}
下面的例子是对的
static int a[10];
void func()
{
memset(a,0,sizeof(a);
}
- memcpy
内存复制
void *memcpy(void *destin,void *source,unsigned n)
以source
指向的地址为起点,将连续的n
个字节数据,复制到以destin
指定的地址为起点的内存中,返回一个指向destin
内存的指针(注意返回的区别)
https://blog.csdn.net/GoodLinGL/article/details/114602721
char a[10] = "abcdefgh";
unsigned n = 2;
cout<<&a<<endl; // 0x61fe06
void * p = memcpy(a+3, a, n); // p指向a,a
// memcpy(a+3, a, n); // p指向a,a
cout<<a<<endl; // abcabfgh
cout<<&a<<endl; // 0x61fe06
cout<<p<<endl; // 0x61fe09
cout<<(char *)p<<endl; // abfgh
memove 允许数据重叠
memcpy 不允许数据的内存空间存在重叠
3. 多维数组
创建数组:可以按行创建,也可以连续创建。部分赋值创建的时候,其他赋值元素默认初始化为0(注意部分赋值和完全不赋值随机初始化的区别,完全不赋值随机初始化的时候不是0)
如果是全部赋值,第一维度可以不写
可以理解为嵌套
// 按行创建
int a[2][3]={{1,2,3},{4,5,6}};
// 按连续内存创建
int a[2][3]={1,2,3,4,5,6};
// 部分赋值
int a[2][3]={{1},{2},{3}};
int a[2][3]={{0,1},{0,1,2},{3}}
// 第1维度自动推导
int a[][3]={1,2,3}
int a[][3]={1,2,3};
for (int i=0;i<1;i++){
for (int j=0;j<3;j++){
printf("%d\n",a[i][j]); // 可以打印但是不能赋值
}
}
// 注意!!!
// 下面的初始化是错误的,无法实现自动推导
// int a[][3];
// for (int i=0;i<3;i++)
// {
// for (int j=0;j<3;j++)
// {
// a[i][j]=i*j;
// }
// }
// 嵌套理解
a[3][4] 中 a[0]是一个数组(也是一个一维数组名)
3.1 二维数组与一维数组的关系
a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]
int arr[m] [n]
arr数组有m个元素,每个元素的类型是一维数组int [n]
3.2 多维数组与sizeof
int a[2][3];
for (int i=0;i<2;i++)
{
for (int j=0;j<3;j++)
{
a[i][j]=i*j;
}
}
int (*p)[3]=a;
printf("%d\n",sizeof(a)); // 24
printf("%d\n",sizeof(a[0])); // 12(因为相当于一个一维数组名)
printf("%d\n",sizeof(p)); // 8
printf("%d\n",sizeof(p[0])); // 12
3.3 多维数组与指针
https://blog.csdn.net/L_fengzifei/article/details/126411708
a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]
对于一维数组及其指针
type *p=arr
p+1相当于移动了一个type类型的字节
对于二维数组及其指针,p+1应该移动整个一维数组对应的字节,type (*p)[n]
,(*p) 表示指针,int [n] 表示指针指向的数据类型
也就是每行的数据类型
int a[1][3]={1,2,3};
for (int i=0;i<1;i++){
// for (int j=0;j<3;j++){
// printf("%d\n",a[i][j]);
// }
printf("%p\n",a[i]); // &a[i][0] 与a[i] 等价
printf("%p\n",&a[i][0]); // &a[i][0] 与a[i] 等价
printf("%d\n",a[i][0]); // 这是数值
}
int a[2][3]={{1,2,3},{4,5,6}};
// 下面三个是等价的
// *a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]*
printf("%p\n",*a);
printf("%p\n",a[0]);
printf("%p\n",&a[0][0]);
// 下面是等价的
int (*p)[3]=a;
printf("%p\n",*(p+1));
printf("%p\n",*(a+1));
printf("%p\n",a[1]);
printf("%p\n",&a[1][0]);
重要
a=&a[0]
a+i=&a[i]
a[i]=&a[i][0]
*a=a[0]
*(a+i)=a[i]
*a[i]==a[i][0]
重要
a[i]==*(a+i)==p[i]==*(p+i)
a[i][j]==*(a[i]+j)==*(*(a+i)+j)==p[i][j]==*(p[i]+j)==*(*(p+i)+j)
int (*p)[3]=a;
for (int i=0;i<2;i++)
{
for (int j=0;j<3;j++)
{
printf("%d,%d,%d,%d,%d,%d\n",a[i][j],*(a[i]+j),*(*(a+i)+j),p[i][j],*(p[i]+j),*(*(p+i)+j));
}
}
/* /// 补充 ///
// 理解
对于1维数组
int a={1,2,3};
int* p=a;
a[1]==*(P+1)==*(a+1)==p[1]
&a[1]==p+1==a+1
// 二维数组
// 看成嵌套的一维数组
int a[][2]={1,2,3,4};
int (*p)[2]=a;
*(*(p+1)+1) ==a[1][1]
p指向了一个都是指针的数组(首地址),而数组元素的每个指针,指向了一个一维数组(首地址)
p+1==a[1]==&a[1][0]
*(p+1)==a[1]==&a[1][0] // 这个记忆方法放到下面可以理解
// 下面三个方法是等价的
printf("%d\n",*(a[1]+1)); //a[1]也是一个指针,表示一维数组
printf("%d\n",*(*(p+1)+1)); // *(p+1) p+1表示一个一维数组,
printf("%d\n",a[1][1]);
*/
重要 - 第二种表达方法
int a[2][3]={{1,2,3},{4,5,6}};
int *p=&a[0][0]; // p=a p=&a[0] *p=a[0] a=&a[0] a[0]=&a[0][0]
for (int i=0;i<2;i++)
{
for (int j=0;j<3;j++)
{
printf("%d,%d\n",a[i][j],*((p+i*3)+j));
}
}
4. 字符型数组
4.1 字符数组
对于字符型数组,赋值个数小于开辟的内存空间,则初始化为
"\0"
,printf("%c","\0")
打印不出来任何东西
字符型数组与数值数组的基本性质是一样的
字符数组就是字符串
c语言中没有字符串类型,所以借用字符数组存放一个字符串
char str[10]={0} // 默认初始化为\0
// 一维
char s[10];
// 多维
char s[2][3];
// 部分赋值
char c[20]={' ','2'};
// 全元素赋值,自动推导长度,这个属于字符数组,不属于字符串
char d[]={'h','e',...};
// 注意!!!
char s[10];
// s="hello world"; // 这种方式不行
s[0]='0'; // 只能是下面这种方式
s[1]='2';
4.2 C语言字符串
4.2.1 字符串创建/访问
字符串 其实是字符数组
字符数组就是字符串
c语言中没有字符串类型,所以用 字符数组存放一个字符串
可以通过索引获得元素字符
在创建字符串的时候不能超出开辟的范围,不然会出错
注意:下面创建的字符串,具有读写权限
注意:如果复制直接"string"复制,而是逐个字符赋值给字符数组,要想创建字符串就要手动加上\0
char s[30]={"hello world"}; // 等价于char s[30]={'h','e',...};
// 其他常用方式
char s[10]="hello world";
char s[]={"hello world"};
char s[]="hello world";
// 为了避免\0的出现,最好的解决办法是
char s[]={0}; // 进行默认初始化
// 索引
char a[10]="hello";
printf("%c\n",a[1]);
长度
strlen
计算不包括结尾的\0
sizeof
计算包含结尾的\0
#include <string.h> // 必须要进行包含
char a[]="hello";
printf("%d\n",sizeof(a)); // 6
printf("%d\n",strlen(a)); // 5
// sizeof 要么等价于实际开辟的内存空间,要么要包含`\0`
char a[10]="hello";
printf("%d\n",sizeof(a)); // 10 , 注意这是要开辟的数组!!!
printf("%d\n",strlen(a)); // 5
// 对于数组越界 !!!
char a[3]="hello";
printf("%s\n",a); // 会发生未知字符
printf("%d\n",sizeof(a)); // 3
printf("%d\n",strlen(a)); //6 随机值,无法准确判断
char a[6]="hello worlld";
printf("%d\n",strlen(a)); // 9 是随机值,无法准确判断
第二种创建方式
char s1[]="string1";
char *s2="string2"
// 初始化注意事项
// char s1[10];
// s1="hello"; // 这种不行
char *s2;
s2="world"; // 这种可以
char s1[]="string"
这种方法定义的字符串所在的内存既有读取权限又有写入权限(可变),可以用于输入与输出函数
[]
可以指明字符串的长度,如果不指明则根据字符串自动推算
声明字符串的时候,如果没有初始化,(由于无法自动推算长度,只能手动指定)char s[10]
char *s1="string"
这种方法定义的字符串所在的内存只有读取权限,没有写入权限(不可变),只能用于输出函数
对于 字符串修改
https://blog.csdn.net/qq_31347869/article/details/105877116!!!
也就是数组形式的字符串可以进行字符串修改(具有读写权限)
指针形式的字符串不可以进行修改(只有读权限)
注意:
数组型字符串不可以进行互相赋值
指针型字符串可以进行相互赋值
// 下面是错的
// char a1[10];
// char a2[10]="hello"; // 这一个表达式是对的
// char a3[10]=a2;
// 下面是可以的
char *a1="";
char *a2="hello";
printf("%s\n",a1);
a1=a2;
printf("%s\n",a1);
字符串访问
char arr1[]="hello world";
char *arr2="hello cpp";
for (int i=0;i<11;i++)
{
printf("%c,%c",arr1[i],*(arr1+i));
}
printf("\n");
for (int i=0;i<9;i++)
{
printf("%c,%c\n",arr2[i],*(arr2+i));
}
printf("%s\n",arr1); // hello world
printf("%s\n",arr2); // hello cpp
printf("%s\n",arr1+1); // ello world
printf("%s\n",arr2+2); // llo cpp
4.2.2 字符串细节
4.2.2.1 \0
\0
不能显示,也没有控制功能
字符串以\0
结尾,注意字符数组不以\0
结尾
字符串逐个扫描,一旦遇到\0
结束处理
"string"会在字符串自动在末尾添加
\0
字符串的字符个数,要包括后面结尾的\0
创建的时候,要为\0,多开辟一个内存空间,所以总数要比字符串+1
// 创建字符串数组,下面这种方式只能逐个元素创建
// 当创建的字符个数小于开辟的数量,剩下的为随机初始化的内容,有可能并不是\0,注意随机初始化和部分初始化的
// 所以输出的可能字符个数可能大于30,因为printf直到\0的时候才会结束输出
char s[30];
char c;
int i;
for (c=65,i=0;c<=90;c++,i++){
s[i]=c;
}
printf("%s\n",s)
// 手动添加\0
// \0 的ASCII编码值是0
// 下面两个等价
s[i]=0;
s[i]='\0';
4.2.3 字符串/地址/指针
字符串与地址
char a='c';
printf("%p\n",&a);
printf("%p\n",a); // 这两个地址不同,第二个写法应该取不出地址
char b[10]="hello";
printf("%p\n",&b);
printf("%p\n",b); // 字符串的名字是地址
printf("%p\n",&b[0]); // 也是字符串第一个元素的地址
int c[10]={1,2,3};
printf("%p\n",&c);
printf("%p\n",c); // 数组名也是地址
printf("%p\n",&c[0]); // 也是数组第一个元素的地址
字符串与指针
https://blog.csdn.net/L_fengzifei/article/details/126411708
char* func()
{
return "string"
}
对于字符串,整个引号中的内容作为指向该字符串存储位置的指针 !!!
// are字符串的第一个字符的地址
// 字符串可以当做指针*"family"相当于取指针指向的元素,取地址的元素,也就是该字符串的首元素
printf("%s,%#p,%c\n","we","are",*"family"); // we,0x409001,f
遍历的一种写法
void func(char *str)
{
while(*str) // '\0' 的ASCII 是0
{
*str=toUpper(*str); // 自己赋值给自己
str++;
}
}
字符串数组形式与指针形式的区别
数组初始化是从静态存储区把一个字符串复制给数组
指针初始化只是复制字符串的地址
《c语言-函数 返回值与局部变量一节的例子》
char arr1[]="hello"; // 栈区
char *arr2="world"; // 常量区
*char 字符串及其地址
char *ptr=new char[10];
printf("%#p\n",&ptr[0]); // 0x7d14d0
printf("%#p\n",ptr); // 0x7d14d0
printf("%#p\n",&ptr); // 0x61fe18
printf("%#p\n",(char *)ptr); // 0x7d14d0
printf("%#p\n",(void *)ptr); // 0x7d14d0
4.2.4 字符串相关函数
strcat/strcpy/strcmp
// strcat 字符串拼接 // inplace操作
char a1[20]="hello";
char a2[10]="world";
printf("%d\n",sizeof(a1)); // 20
printf("%d\n",sizeof(a2)); //10
strcat(a1,a2);
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 20
char a1[]="hello";
char a2[]="world";
printf("%d\n",sizeof(a1)); // 6
printf("%d\n",sizeof(a2)); // 6
strcat(a1,a2);
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 6 ???
// 没有写入权限,失败
// char *a1="hello";
// char *a2="world";
// printf("%d\n",sizeof(a1)); // 20
// printf("%d\n",sizeof(a2)); //10
// strcat(a1,a2); // 由于没有写入权限所以不行
// printf("%s\n",a1);
// printf("%d\n",sizeof(a1)); // 20
// strcpy 字符串复制 // inplace操作
// 会将原来的数据全部进行覆盖
// 值得注意的是: strcpy会把\0自动添加进去
char a1[]="hellohello";
char a2[]="world";
printf("%d\n",sizeof(a1)); // 11
printf("%d\n",sizeof(a2)); // 6
strcpy(a1,a2); //world
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 11 ???
char a1[20]="hellohello";
char a2[10]="world";
printf("%d\n",sizeof(a1)); // 20
printf("%d\n",sizeof(a2)); // 10
strcpy(a1,a2); //world
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 20
问题
// 对于数组越界 !!!
char a[3]="hello";
printf("%s\n",a); // 会发生未知字符
printf("%d\n",sizeof(a)); // 3
printf("%d\n",strlen(a)); //6
char name[5];
strcpy(name,"zhangsan");
printf("%s\n",name); // 不发生越界??? 有的编译器会报错warning
数值型/字符串转换
把数字存储为字符串就是存储数字字符
123 -> ‘1’ ‘2’ ‘3’ ‘\0’
字符串转数值型
atoi
字符串转整型
// 下面两种字符串都可以
char s[]="123";
char *s="123";
int var=atoi(s);
printf("%d\n",var);
atof
字符串转整型
// 下面两种字符串都可以
// char s[]="123";
char *s="123.456";
double var=atof(s);
printf("%f\n",var);
数值型转字符串
itoa()
#include<stdlib.h>
int a=10;
char s[10]={0};
itoa(a,s);
printf("%s\n",s)
char a='a';
char buffer[10];
itoa(a,buffer,2); // 2表示转换基数
sprintf
// 整型转字符串
int a=120;
char str[20];
memset(str,0,sizeof(str));
sprintf(str,"%d",a);
printf("%s\n",str);
printf("%d\n",sizeof(str));
4.3 字符串数组
https://blog.csdn.net/cnds123321/article/details/122973636
https://blog.csdn.net/u014157109/article/details/118718978
第一种创建方式
char strs[row][col] // row 表示有几个字符串,col每个字符串的最大长度
char strs[2][10]={"hello","world"};
// 下面这种是错误的
// char strs[2][10];
// strs={"hello","world"};
// 上面相当于,因为对于一维来说这也是错误的,a是常量
// char a[10];
// a="hello";
第二种创建方式
char* strs[row] // row 表示有几个字符串,每个字符串的类型是char* ;不能写成char (*strs)[row] 这相当于二维字符数组,每个字符数组有row个字符!!!
char* strs[10]={"hello","world"};
strs[0] 指向第一个字符串的首字符的地址,*strs[0]=='h'
strs[1] 指向第二个字符串的首字符的地址,*strs[1]=='w'
// 下面这种是错误的
// char* strs[10];
// strs={"hello","world"};
指针数组 – 还得再看
数组中所有元素保存的都是指针,就叫做指针数组
dataType *arr[]
,dataTye* (arr[])
表明是一个数组,每个元素的类型都是dataType*
注意 指针数组和二级数组的区分
int* arr[3]={&a,&b,&c};
int** p=arr; // 定义一个指向指针数组的指针,相当于(int*)* p=arr
char* str[2]={"hello","world"};
printf("%s\n",str[0]); // 输出"hello",char* s="hello";
//下面的方法更好理解
char* s1="hello";
char* s2="world";
char* s[2]={s1,s2};
/*
char *s="hello";
char *s;
s="hello";
*/
6. 结构体数组
数组中每个元素都是结构体
struct stu{
char* name;
int num;
}class[5];
// 声明的时候初始化
struct stu{
char* name;
int num;
}class[5]={
{"li",1},
{"w",2}
};
// 声明的时候初始化,自动推断元素个数
struct stu{
char* name;
int num;
}class[]={
{"li",1},
{"w",2}
};
// 可以在结构体外,创建结构体数组
struct stu class[]={
{"li",2},
{"wang",3}
};
// 访问数据
class[0].name;
class[1].num=2; //修改
6.1 结构体数组指针
因为是数组,所以数组名可以作为元素的首地址
struct stu class[]={
{"li",2},
{"wang",3}
};
// 下面两个是等价的,都表示地址
printf("%p\n",class);
printf("%p\n",&class[0]);
// 所以可以利用指针指向地址
struct stu* pstu=class;
// 下面这两种访问方法都是等价的
struct stu* pstu=class; // 看成普通数组就行
for (int i=0;i<2;i++){
printf("%s,%d\n",(pstu+i)->num,(pstu+i)->age);
}
for (int i=0;i<2;i++,pstu++){
printf("%s,%d\n",pstu->num,pstu->age);
}
6.2 结构体数组/指针 进阶
结构体名 不是该结构体的起始地址,更不是该结构体首元素的地址
结构体的起始地址,在值上 等于 该结构体首元素的地址
#include <iostream>
#define LEN 10
using namespace std;
struct names
{
char first[LEN];
char last[LEN];
};
struct people
{
struct names handle;
char favfool[LEN];
char job[LEN];
float income;
};
int main()
{
// 结构体名 不是该结构体的起始地址
// 结构体的起始地址,在值上 等于 该结构体首元素的地址
struct names name1={"li","wang"};
// std::cout<<name1<<endl;
printf("%p\n",name1); // 0x61fd40 不等于首元素的地址
printf("%p\n",&name1); // 0x61fdf0 相当于整个结构体的起始地址 而结构体的起始地址 的值 等于第一个成员的地址
printf("%p\n",name1.first); // 0x61fdf0
printf("%p\n",&name1.first); // 0x61fdf0 该成员(整个成员)的起始地址
printf("%p\n",&name1.first[0]); // 0x61fdf0 等价于 name1.first
struct people people1={
{"li","wang"},
"apple",
"ex",
20.1
};
printf("%s\n",people1.handle.first);
printf("%s\n",people1.handle.last);
printf("%p\n",people1); // 0x61fd10 !!! 说明结构体名不等于首元素的地址也不等于结构体的起始地址
printf("%p\n",&people1); // 0x61fdc0 整个外层结构体的起始地址
printf("%p\n",people1.handle); // 0x61fd10 !!! 说明结构体名不等于首元素的地址也不等于结构体的起始地址
printf("%p\n",&people1.handle); // 0x61fdc0 整个内存结构体的起始地址 等于 整个外层结构体的起始地址
printf("%p\n",people1.handle.first); // 0x61fdc0 下面三个是数组的地址
printf("%p\n",&people1.handle.first); // 0x61fdc0
printf("%p\n",&people1.handle.first[0]); // 0x61fdc0
struct people peoples[2]={
{
{"li","wang"},
"apple",
"ex",
20.1
},
{
{"zhang","san"},
"pea",
"ex2",
22.1
},
};
// 结构体指针
struct people *ptr1;
ptr1=&people1; // 结构体的名字并不等于结构体元素的首地址!!! 指向起始地址是对的,因为类型是匹配的
cout<<ptr1->favfool<<","<<ptr1->job<<","<<ptr1->income<<endl;
// 结构体数组指针 把他想象成普通数组的指针就行了
struct people * ptr;
// ac - 84 = int 44 一共44个字节
cout<<&peoples[0]<<" "<<&peoples[1]<<endl; // 0x61fd80 0x61fdac 这个相等于起始地址
cout<<&peoples<<endl; // 0x61fd80 整个数组的地址
cout<<peoples<<endl; // 数组首元素的地址,也就是第一个结构体的地址
// 结构体的名字并不等于结构体元素的首地址!!!
// version1
ptr=peoples;
cout<<ptr<<" "<<ptr+1<<endl; // 0x61fd80 0x61fdac
cout<<ptr->job<<endl;
// version2
ptr=&peoples[0]; // struct people 所以+1 跨过了整个数组
cout<<ptr<<" "<<ptr+1<<endl; // 0x61fd80 0x61fdac ptr+1跨过了整个结构体数组中的一个元素
cout<<ptr->income<<endl;
return 0;
}