C语言万字基础笔记总结(二)
文章目录
-
C语言学习笔记,记录所学,便于复习。 由于篇幅过大,考虑到观感,准备分多篇记录。
-
学习视频链接:《带你学C带你飞》
-
IDE:Dev-C++ 5.11
-
为了练习编程习惯,暂时放弃Clion
-
前排提醒:建议收藏
系列文章链接
一、数组
1、数组的定义
- 格式:类型 数组名[元素个数]
如:int i[5]
2、访问数组元素
- 格式:数组名[下标]
如:
i[0]; // 访问i数组中第1个元素
i[3]; // 访问i数组中第4个元素
注:数组的下标是从0开始的,下标0对应数组第一个元素
3、数组的初始化
- 将数组中所有元素初始化为0
int a[5] = {0};
//事实上,这个只是把第一个元素赋值为0,其余各位是自动初始化为0
int a[5] = {1};
//这里就是,第一个元素为1,其余元素为0
- 如果是给元组赋予不同的值,用逗号隔开即可
int a[5] = {1, 2, 3, 4, 5};
- 也可以只给一部分元素赋值,未被赋值的元素,自动初始化为0
int a[5] = {1, 2, 3};
//前三位成功赋值,后两位,自动初始化为0
- 也可以指定元素进行赋值,未被赋值的元素自动初始化为0
int a[5] = {[2] = 2, [4] = [4]};
//数组a的第3位被赋值为2,第5位被赋值为4,因为数组的下标是从0开始的!!!
- 也可以只给出元素的值,不指定数组的长度
(编译器会根据值的个数自动判断数组的长度)
int a[] = {1, 2, 3, 4, 5}
二、 二维数组
1、二维数组的定义
- 格式:类型 数组名[常量表达式] [常量表达式]
2、二维数组的访问
- 格式:数组名[下标][下标]
如:
a[0][0]; // 访问a数组中第一行第一列的元素
b[2][3]; // 访问b数组中第三行第四列的元素
3、二维数组的初始化
- 由于二维数组在内存里面中是线性存放的,因此可以将所有的数据写在一个花括号里面
int a[2][3] = {1, 2, 3, 4, 5, 6};
那到底是怎么存放的呢,用程序打印一下,观察结果。
#include <stdio.h>
int main()
{
int a[2][3] = {1, 2, 3, 4, 5, 6};
int i, j;
for (i = 0;i < 2;i++)
{
for (j = 0;j < 3;j++)
{
printf("a[%d][%d] = %d\n", i, j, a[i][j]);
}
}
return 0;
}
结果如下:
a[0][0] = 1
a[0][1] = 2
a[0][2] = 3
a[1][0] = 4
a[1][1] = 5
a[1][2] = 6
通过结果,是不是很容易看出来了~
- 更直观的表示元素的分布,可以用大括号将每一行的元素括起来
int a[2][3] = {{1, 2, 3}, { 4, 5, 6}};
或者写成这种样式:
int a[][3] = {
{1, 2, 3},
{4, 5, 6}
};
以上这两种形式的行数都可以省略不写
- 将整个二维数组初始化为0,则只需要在大括号里写一个0
int a[2][3] = {0};
- 对某些指定的元素进行初始化,其它元素自动初始化为0
int a[2][3] = {[0][0] = 1, [1][2] = 2};
三、指针
1、定义指针变量
- 格式:类型名 *指针变量名
char *pa; //定义一个指向字符型的指针变量
int *pb; // 定义一个指向整型的指针变量
取地址运算符和取值运算符
-
如果需要获取某个变量的地址,可以使用取地址运算符(&)
如:char *pa = &a; -
如果需要访问指针变量指向的数据,可以使用取值运算符(*)
如:printf("%c, %d\n", *pa, *pb);
注意:
值得一提的是 *
这个符号,在定义指针需要它,而在同时又是取值运算符。需要慢慢理解
避免使用未初始化的指针
int *p //例如这样就是未初始化的指针
-----------------------------
int *p = &a //例如这样就是初始化过的指针
- 指针的小例子
# include <stdio.h>
int main(){
char a = 'H';
int b = 521;
char *pa = &a;
int *pb = &b; // 这里的*是用来定义指针变量
printf("a=%c\n", *pa); // 这里打印的是地址指向的那个变量值
printf("b=%d\n", *pb); // 这里的*是用来取值
printf("sizeof pa = %d\n", sizeof(pa));
printf("sizeof pb = %d\n", sizeof(pb));
printf("a的地址:%p\n", pa); // 这里打印的是指针存放的地址
printf("b的地址:%p\n", pb);
return 0;
}
结果:
a=H
b=521
sizeof pa = 8
sizeof pb = 8
a的地址:000000000062FE0F
b的地址:000000000062FE08
数组名其实是数组的第一个元素的地址
# include <stdio.h>
int main(){
char str[3];
printf("请输入最帅的博主:");
scanf("%s", str);
printf("最帅博主的地址是:%p\n", str); // 查看数组名的地址
printf("最帅博主的地址是:%p\n", &str[0]); // 取数组第一个元素的地址
return 0;
}
运行结果:
请输入最帅的博主:远方的星
最帅博主的地址是:000000000062FE10
最帅博主的地址是:000000000062FE10
那数组其它位置的地址与首地址是什么关系呢?
# include <stdio.h>
int main(){
char a[] = "Hello";
int b[] = {1, 2, 3, 4, 5};
float c [] = {1.1, 2.2, 3.3, 4.4};
double d[] = {1.1, 2.2, 3.3, 4.4};
printf("a[0] -> %p, a[1] -> %p, a[2] -> %p\n", &a[0], &a[1], &a[2]);
printf("b[0] -> %p, b[1] -> %p, b[2] -> %p\n", &b[0], &b[1], &b[2]);
printf("c[0] -> %p, c[1] -> %p, c[2] -> %p\n", &c[0], &c[1], &c[2]);
printf("d[0] -> %p, d[1] -> %p, d[2] -> %p\n", &d[0], &d[1], &d[2]);
return 0;
}
运行结果:
a[0] -> 000000000062FE10, a[1] -> 000000000062FE11, a[2] -> 000000000062FE12
b[0] -> 000000000062FDF0, b[1] -> 000000000062FDF4, b[2] -> 000000000062FDF8
c[0] -> 000000000062FDE0, c[1] -> 000000000062FDE4, c[2] -> 000000000062FDE8
d[0] -> 000000000062FDC0, d[1] -> 000000000062FDC8, d[2] -> 000000000062FDD0
这里是十六进制,数组a,char类型,依次增加1个字节;数组b,int类型,依次增加4个字节;数组c,float类型,依次增加4个字节;数组d,double类型,依次增加8个字节。与下表相对,不难发现其规律,依次增加的大小为数据类型占的字节数。
- 64位编译器
char | 1个字节 |
---|---|
char* | 8个字节 |
short int | 2个字节 |
int | 4个字节 |
unsigned int | 4个字节 |
float | 4个字节 |
double | 8个字节 |
long | 8个字节 |
long long | 8个字节 |
unsigned long | 8个字节 |
2、指针的运算
当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素。
- 例:
# include <stdio.h>
int main(){
char a[] = "Hello";
char *p = a;
printf("*p = %c, *(p+1) = %c, *(p+3) = %c\n", *p, *(p+1), *(p+3));
return 0;
}
结果:
*p = H, *(p+1) = e, *(p+3) = l
由此可见,加减代表的是方向。p+1指的是,指向数组的下一个元素,那么p-1,便是指向数组的上一个元素。
- 这里使用指针间接访问数组元素的方法叫做指针法。
3、指针和数组的区别
- 数组名只是一个地址,而指针是一个左值。
lvalue(左值):C语言中指用于识别或定位一个存储位置的标识符,左值必须是可改变的。
4、指针数组
- 例如:
int *p[5]
- 指针数组是一个数组,每个数组元素存放一个指针变量。
在指向字符指针的时候非常好用。
# include <stdio.h>
int main(){
char *p[] = {
"选择了就不必害怕",
"努力了就一定成功",
"加油,陌生人!"
};
int i;
for (i = 0;i < 3;i++){
printf("%s\n", p[i]); // 这里是不能写成*p[i]的,这样的意思是指向数组的第一个元素地址
}
return 0;
}
5、数组指针
- 例如:
int (*p)[5]
- 数组指针是一个指针,它指向的是一个数组
例:这里使用数组指针完成的
# include <stdio.h>
int main(){
int temp[5] = {1, 2, 3, 4, 5};
int (*p2)[5] = &temp; //指向数组的指针
int i;
for (i = 0;i < 5;i++)
{
printf("%d\n", *(*p2 + i));
}
return 0;
}
运行结果:
1
2
3
4
5
- 比较:使用(普通)指针
(数组名其实是数组的第一个元素的地址)
结果上虽然相等,但本质上是有所区别的。。。
# include <stdio.h>
int main(){
int temp[5] = {1, 2, 3, 4, 5};
int *p = temp; //指向数组的第一个元素的指针
int i;
for (i = 0;i < 5;i++)
{
printf("%d\n", *(p + i));
}
return 0;
}
6、指针和二维数组
arry指向的是包含5个元素的数组的指针
把一个地址的值取出来,称之为 “解引用”
解引用到底是什么,查了一些资料,是否真的是 “取指针指向的地址的内容” 还不得而知,还需要日后慢慢的沉淀。
解引用:
*(array+i) == array[i]
*(*(array+i)+j) == array[i][j]
*(*(*(array+i)+j)+k) == array[i][j][k]
下标索引的形式都可以转换为使用指针间接索引的形式。
- 例:
# include <stdio.h>
int main(){
int array[4][5] = {0};
int i,j,k = 0;
for (i=0;i<4;i++)
{
for(j=0;j<5;j++)
{
array[i][j] = k++;
}
}
printf("*(array+1):%p\n", *(array + 1));//array+1的和进行解引用
printf("array[1]:%p\n", array[1]);//array第二行首地址
printf("&array[1][0]:%p\n", &array[1][0]);//对 array[1][0]取地址
printf("**(array+1):%d\n", **(array+1));//对array+1的和的解引用进行解引用
printf("array[1][0]:%d\n", array[1][0]);//array[1][0]的值
return 0;
}
运行结果:
*(array+1):000000000062FDC4
array[1]:000000000062FDC4
&array[1][0]:000000000062FDC4
**(array+1):5
array[1][0]:5
7、void指针
- void指针又称通用指针,就是可以指向任意类型的数据。任何类型的指针都可以赋值给void指针。
- 例:验证一下:
# include <stdio.h>
int main(){
int num = 521;
int *p1 = #
char *p2 = "天道酬勤";
void *p3;
p3 = p1;
printf("p1:%p, p3:%p\n", p1, p3);
p3 = p2;
printf("p2:%p, p3:%p\n", p2, p3);
return 0;
}
结果如下:
p1:000000000062FE04, p3:000000000062FE04
p2:0000000000404000, p3:0000000000404000
得到的结果是一样的,由此可见,是可以将任意数据类型的指针赋值给void的指针。
但如果要根据void指针,取出其内容,最好进行数据类型的强制转换。
- 根据上述例子进行修改:
# include <stdio.h>
int main(){
int num = 521;
int *p1 = #
char *p2 = "天道酬勤";
void *p3;
p3 = p1;
printf("p3:%d\n", *(int *)p3);//(int *)p3是进行类型强制转换,再加一个*,进行解引用,才能取出值
p3 = p2;
printf("p3:%s\n", (char *)p3);//由于字符串的特殊性,就不需要进行进引用了
return 0;
}
结果如下:
p3:521
p3:天道酬勤
8、NULL指针
NULL指针又称空指针,它有啥作用呢?
- 有利于养成良好的编程习惯
当不清楚要将指针初始化为哪个地址时,可以将它初始化为NULL;在对指针进行解引用时,先检查该指针是否为NULL。可以有效避免野指针
9、指向指针的指针
- 好处:
①避免重复分配内存
②只需要进行一处修改
10、指向常量的指针
之前见过用宏定义来定义常量,其实还可以利用const
关键字。
- const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的,即只能读不能重写。
例:
# include <stdio.h>
int main(){
const float pi = 3.14;
printf("%f\n",pi);
pi = 3.1415;
return 0;
}
编译之后,会直接报错!
[Error] assignment of read-only variable 'pi'
// [错误]只读变量'pi'的赋值
由此可以得到关于指向常量的指针的小结论:
指向常量的指针
- 指针可以修改为指向不同的常量
- 指针可以修改为指向不同的变量
- 可以通过解引用来读取指针指向的数据
- 不可以通过解引用修改指针指向的数据
11、常量指针
①、指向非常量的常量指针
- 指针自身不可以被修改
- 指针指向的值可以被修改
例如:
# include <stdio.h>
int main(){
int num = 521;
const int cnum = 1024;
int * const p = #
*p = 7758;//对指针指向的值进行修改
printf("*p:%d\n", *p);
return 0;
}
输出为:
*p:7758
将上述例子加以修改:
# include <stdio.h>
int main(){
int num = 521;
const int cnum = 1024;
int * const p = #
p = &cnum;//对指针本身进行修改
printf("*p:%d\n", *p);
return 0;
}
编译结果,系统会报错!
[Error] assignment of read-only variable 'p'
②、指向常量的常量指针
- 指针自身不可以被修改
- 指针指向的值也可以不被修改
(和上例类似)
四、未完待续
指针真的是C的灵魂所在,欲速则不达,花的时间也比较长,不断练习才能更好地理解。
这一阶段的笔记就先记录到这里,但学C的道路仍然还很漫长。。。
路漫漫其修远兮,吾将上下而求索。
大家一起加油吧!
作者:远方的星
CSDN:https://blog.csdn.net/qq_44921056
本文仅用于交流学习,未经作者允许,禁止转载,更勿做其他用途,违者必究。
文章来源: luckystar.blog.csdn.net,作者:爱打瞌睡的CV君,版权归原作者所有,如需转载,请联系作者。
原文链接:luckystar.blog.csdn.net/article/details/118852544
- 点赞
- 收藏
- 关注作者
评论(0)