C语言万字基础笔记总结(二)

举报
爱打瞌睡的CV君 发表于 2022/07/08 00:02:16 2022/07/08
【摘要】 文章目录 系列文章链接一、数组1、数组的定义2、访问数组元素3、数组的初始化 二、 二维数组1、二维数组的定义2、二维数组的访问3、二维数组的初始化 三、指针1、定义指...


  • C语言学习笔记,记录所学,便于复习。 由于篇幅过大,考虑到观感,准备分多篇记录。

  • 学习视频链接:《带你学C带你飞》

  • IDE:Dev-C++ 5.11

  • 为了练习编程习惯,暂时放弃Clion

  • 前排提醒:建议收藏


系列文章链接

C语言万字基础笔记总结(一)


一、数组

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 = &num;
	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 = &num;
	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 = &num;
	
	*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 = &num;
	
	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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。