C语言基础入门学习--学习笔记

C语言标准

  • 1983年美国国家标准局(ANSI),开始制定C语言标准的工作
  • 1989年该标准正式公布,一般称为ANSI C(C89或C90)
  • 1999年ANSI/ISO联合委员会进行修改,增加了一些功能,称为C99
  • 2011年又增加了新的标准称为C11

C语言现状

  • 学好C语言,走遍天下都不怕
  • 编程语言排名

工具

  • 所有程序均基于vs2019平台

第一个C程序

c程序都是由若干个头文件和函数组成

#include 
int main()
{
	printf("Hello World!");
	return 0;
}
  • #include 是预处理指令
  • int代表函数执行完成后会返回一个整型
  • ()代表函数,一个程序只有一个主函数,是程序的入口
  • {}代表程序的范围
  • printf()是输出函数,功能是在屏幕上输出""内的内容,是c语言的库函数
  • return 0是函数的返回值,函数不同,功能不同,返回值不同

关键字

  • 常见关键字
    auto break case char const continue default do double else enum extern float for goto if int long register return short signed
    sizeof static struct switch typedef union unsigned void volatile while

数据类型

char

  • char字符型,用于储存字符,char类型实际上用特定的整数来表示特定的字符,如在ASCII码中,用整数65代表大写的字母A
  • 声明char类型
  • 字符在内存中的存储是以ASCII码存储的,字符型数据在内存中储存的是它的ASCII码值,它是一个字节,所有数据类型在内存中都是以0和1代码二进制储存的,这个原则不会变。在C语言中,char型数据是将一个字符常量放到一个字符变量中,并不是把该字符本身放到内存单元中去,而是将该字符的相应的ASCII代码放到存储单元中

char grade = ‘A’;
char grade = 65; //可以,但这并不是一个好的编程习惯

int

  • int整型,包括正整数、负整数和零

int a = 0;
int b = 22;

float和double

  • float类型必须至少能表示6位有效数字,double(双精度浮点型)至少13位有效数字,默认情况下,编译器假定浮点型常量是double类型,浮点类型的书写可以直接写整数,也可以写有符号的数字(包括小数点)后面紧跟e或E,正号可以省略,可以没有小数点或指数部分,但是不能同时省略两者

float a = 6.3e-33;
double b = 18;
double c = 100.;

数据类型的大小

#include 
int main()
{                                  //单位:字节
	printf("%d\n", sizeof(char));     //1
	printf("%d\n", sizeof(short));    //2
	printf("%d\n", sizeof(int));      //4
	printf("%d\n", sizeof(long));     //4
	printf("%d\n", sizeof(long long));//8
	printf("%d\n", sizeof(float));    //4
	printf("%d\n", sizeof(double));   //8
	printf("%d\n", sizeof(long double));//8
	return 0;
}

C语言变量与常量数据

变量

  • 局部变量:定义在函数内部的变量,它的作用域仅限于函数内部,离开函数后便无法使用,main()函数中定义的变量也是局部变量,只能在main函数内部使用,可以在不同的函数中使用相同的变量名,生命周期从进作用域开始,出作用域结束
  • 全局变量:在所有函数外部定义的变量,作用域是整个程序,包括.c和.h文件,当局部变量和全局变量重名时,优先使用局部变量,是整个程序的生命周期
  • 变量的命名是由字母、数字和下划线组成,只能以字母或下划线开始,不允许使用关键字
#include 
int c = 0;//全局变量若不初始化默认为0
int Add(int a, int b)//形参变量
{
	return a + b;
}
int main()
{
	int a = 18;//局部变量
	int b = 23;
	{
		int a = 22;//局部变量
		printf("a=%d ", a);
	}
	c = Add(a, b);//实参变量
	printf("c=%d ", c);
	printf("a=%d ", a);
	return 0;
}

输出结果:a=22 c=41 a=18
常量

  • 字面常量:如整数常量、浮点数常量、字符常量以及字符串常量等
  • #define定义的标识符常量,通用格式 #define NAME value,末尾不用加分号,NAME大写,增加程序的可读性
  • const修饰的常变量:用于限定一个变量为只读,具有常属性,但本质上还是个变量

const int a = 10;
int arr[a] = {0};//程序报错,说明a是个变量

枚举常量

  • 枚举类型的定义形式,不能对name1…进行赋值,因为他们是常量,只能将他们的值赋给其他变量

enum typeName{ name1 , name2 , name3 ,…};

#include 
#define MAX 10
enum Sex
{
	male,//若改为male=1,则female=2,这是赋初值
	female,
};
int main()
{
    enum Sex s = male;
    //male=10;会报错
	printf("%d ", MAX);//10
	printf("%d ", male);//0
	printf("%d ", female);//1
	return 0;
}

字符串

  • 字符串是一个或多个字符的序列,使用空字符\0作为结束的标志,使用双引号(" ")告知编译器他括起来的是字符串,而单引号(’ ')括起来的是字符,在计算字符长度的时候\0是结束标志,不算字符串的内容
    C语言基础入门学习--学习笔记_第1张图片

转义字符

  • 转移字符以\或\x开头,八进制形式的转义字符最多后跟三个数字即\ddd,十六进制形式的转义字符后最多跟两个数字即\xdd
转义字符 含义 ASCII值
\a 警报 007
\b 退格 008
\f 换页 012
\n 换行 010
\r 回车 013
\t 水平制表符 009
\v 垂直制表符 011
\\ 反斜杠 092
\’ 单引号 039
\" 双引号 034
\? 问号 063
\ddd ddd表示1~3个八进制数字
\xdd dd表示2个16进制数字

控制语句:分支和跳转

if语句

  • if语句被成为选择语句或分支语句,它相当于一个交叉点,程序要在两条分支中选择一条执行

if ( expression )
statement;

如果对expression求值为真(非0),则执行statement,statement可以是简单语句也可以是复合语句

if else语句

  • 两者选其一执行,如果expression1为真,则执行statement1部分,否则执行statement2部分

if ( expression )
statement1;
else
statement2;

if else嵌套语句

  • 建议每条statement语句都使用{},增强程序的可读性,准确性,否则,else总是与它最近的if进行配对

if ( expression )
statement1;
else if
statement2;

else
statement3;

开关语句:switch

  • 在switch语句中没有办法直接实现分支,需要搭配break,如果没有break,程序会继续向下执行
  • 在最后加个default语句,甚至可以其后加break

switch(整型表达式)
{
case 整型常量:语句;
}

#include 
int main()
{
	int day = 0;
	scanf("%d", &day);
	switch (day)
	{
	case 1:
		printf("星期一");
		break;
	case 2:
		printf("星期二");
		break;
	case 3:
		printf("星期三");
		break;
	case 4:
		printf("星期四");
		break;
	case 5:
		printf("星期五");
		break;
	case 6:
		printf("星期六");
		break;
	case 7:
		printf("星期七");
		break;
	default:
		printf("输入错误");
	}
	return 0;
}

控制语句:循环语句

while循环

  • while循环中的表达式为真时,程序会执行,表达式为假则不会执行

while ( expression)
{
statement;
}

#include //打印1-10
int main()
{
	int i = 0;
	while (i < 10)
	{
		i++;
		printf("%d ", i);
	}
	return 0;
}
  • while循环中的 break和continue,只要在循环中遇到break,就会跳出循环,而遇到contine则终止本次循环,也就是continue后的语句不会再执行,而是直接跳转到while语句的判断部分,进行下一次循环的判断
#include 
int main()
{
	int i = 0;
	while (i < 10)
	{
		i++;
		if (5 == i)
			continue;
		else if (9 == i)
			break;
		printf("%d ", i);//1 2 3 4 6 7 8
	}
	return 0;
}

for循环

  • for循环的表达式1是初始化部分,表达式2是条件判断部分,表达式3是调整部分,for循环和while循环里的break和continue的用法一样
  • break只能结束当前的循环,若外部有循环,则不会终止外部的循环
  • 不可在for循环体内修改循环变量,防止for循环失去控制
  • 建议for循环采用“前闭后开”的写法,便于知道循环了几次

for( 表达式1; 表达式2; 表达式3)
{
statement;
}

#include 
int main()
{
	int i = 0;
	for (i = 1; i <= 10; i++)
	{
		if (i == 5)
			continue;
		if (i == 9)
			break;
		printf("%d ", i);//1 2 3 4 6 7 8
	}
	return 0;
}

do-while循环

  • do-while循环是先执行再判断,至少执行一次
  • do-while循环中的break和continue与for和while循环中的作用大同小异

do
{
语句;
}while(表达式);

#include 
int main()
{
	int i = 0;
	do
	{
		i++;
		if (i == 5)
			continue;
		if (i == 9)
			break;
		printf("%d ", i);//1 2 3 4 6 7 8
	} while (i <= 10);
	return 0;
}

函数

库函数

  • C语言库函数是把自定义函数放到库里,是c语言已经写好的一些函数,把这些函数编完放到一个文件里。程序员用的时候把它所在的文件名用#include<>加到里面就可以了,例如:#include
  • C语言常见的库函数包括IO函数、字符串操作函数、字符操作函数、内存操作函数、时间/日期函数、数学函数以及其他库函数
  • 以下是查询工具,尽量看英文,提高自己能力,也可以使用微软的MSDN工具查询
    网站1
    网站2英文版
    网站2中文版

自定义函数

  • 自定义函数与库函数一致,都有函数名、返回值类型以及函数参数
  • 实参是真实传给函数的参数,包含常量、变量、表达式、函数等,无论什么类型,在调用时必须有确定的值,以便将值传给形参
  • 形式参数是形式参数是指函数名后括号中的变量,为形式参数只有在函数被调用的过程中才会分配内存单元,形式参数当函数调用完成之后就自动销毁了,所以形式参数只在函数中有效
  • 如果自定义函数在主函数前,则使用时不需要声明,反之需要提前声明
  • 函数传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
  • 函数传址调用:把函数外部创建变量的内存地址传递给函数参数,这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量
void swap(int* x,int* y)//交换x和y的值,形式参数
{                       //若在主函数后需声明void swap(int* x,int* y); 
	int temp = 0;
	temp = *x;
	*x = *y;
	*y = temp;
}
int main()
{
	int x = 1;
	int y = 2;
	printf("x=%d,y=%d\n", x, y);//1 2 
	swap(&x, &y);//实际参数
	printf("x=%d,y=%d", x, y);//2 1
	return 0;
}
#include 
int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}//答案:4321 ,printf()的会返回字符的个数,先打印43后返回2,在打印2返回1,再打印1

函数递归

  • C允许函数调用它自己,这种调用称为递归,使用递归可以减少代码量,为某些问题提供了简单的方案,但是有的递归会消耗计算机的内存资源,不方便维护和阅读
    递归必须有限制条件,每次递归接近这个限制条件,当达到这个限制条件后,递归结束
#include 
void Print(int n)
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ", n % 10);
}
int main()
{
	int num = 123;
	Print(num);//打印1 2 3
	return 0;
}

C语言基础入门学习--学习笔记_第2张图片

数组

一维数组

  • 数组是由数据类型相同的一系列元素组成
  • 数组由数组类型,数组名以及数组元素多少组成,[]中的必须是常量,也可以不写

数组的初始化:
int arr1[10] = { 1,2,3 };
int arr2[] = { 1,2,3,4 };
int arr3[5] = { 1,2,3,4,5 };
char arr4[3] = { ‘a’,98, ‘c’ };
char arr5[] = { ‘a’,‘b’,‘c’ };
char arr6[] = “abcdef”;
char arr7[] = “abc”;
char arr8[3] = { ‘a’,‘b’,‘c’ };
C语言基础入门学习--学习笔记_第3张图片

arr[]=“abcdef”
siezeof:计算arr所占空间的大小7
strlen:求字符串长度’\0’之前的字符个数,直到找到’\0’为止6
strlen:求字符串的长度,只能求对字符串求长度,需要引用库函数,头文件
sizeof是计算变量、数组、类型的大小,单位是字节,是个操作符
sizeof和strlen之间两者没有联系
sizeof(数组名)计算的是整个数组的大小,单位是字节
&数组名,数组名代表整个数组,取出的是整个数组的地址

#include 
int main()
{
	char arr[] = "abc";
	char arr1[] = { 'a','b','c' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr1));
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr1));//答案是4 3 3 随机值(未找到\0结束标志)
	return 0;
}
  • 数组是通过下标进行访问,下标从0开始
  • 数组的长度可以通过sizeof(arr)/sizeof(arr[0])计算得到
  • 数组在内存中的存储是连续的,由低地址到高地址
    C语言基础入门学习--学习笔记_第4张图片
    二维数组
  • 二维数组的创建和初始化于一维数组类似,不过二维数组可以省略行,但是不能省略列
  • 二维数组也是通过下标进行访问,在内存中的存储也是连续的
  • 数组的下标是有范围制的,数组的下标是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1
  • C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查
    C语言基础入门学习--学习笔记_第5张图片
#include 
int main()
{
	int arr1[3][3];
	int i = 0;
	for (i = 0; i < 2; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("&arr1[%d][%d] = %p\n", i, j, &arr1[i][j]);
		}
	}
	return 0;
}

C语言基础入门学习--学习笔记_第6张图片

操作符

算术操作符

  • +(加法运算符),-(减法运算符),*(乘法运算符),/(除法运算符),%(求模运算符)
  • 除了%运算符外,其余均可用于整数和浮点数
  • %的两个操作数必须为整数
  • 如果/运算符的两个操作数都为整数,执行整数除法,只要有一个浮点数就执行浮点数除法
#include 
int main()
{
	int a = 7/4;
	float b = 7%4;
	float c = 7 / 4.0;
	printf("%d ", a);//1
	printf("%lf ", b);//3.000000
	printf("%lf", c);//1.750000
	return 0;
}

移位操作符
移位操作符的操作数必须是整数

  • 左移操作符<<,左边抛弃,右边补0,
#include 
int main()
{
	int a = 2;//00000000  00000000  00000000  00000010
	int b = a << 1;//0000000  00000000  00000000  000000100=4
	printf("%d\n", b);//4
	printf("%d"\n, a);//2
	return 0;
}
  • 右移操作符>>,右移运算分为两种
    • 1.逻辑移位,左边补0,右边丢弃
    • 2.算术移位,左边用该值的符号位补充,右边丢弃
int main()
{
	int a = -1;//11111111 11111111 11111111 11111111
	int b = a >> 1;//01111111 11111111 11111111 11111111或11111111 11111111 11111111 11111111
	printf("%d\n", b);//当前平台是算术移位结果是-1
	return 0;
}

位操作符

  • &按位与,|按位或 ,^按位异或,它们的操作数必须是整数
#include 
int main()
{
	int a = 1 & 2;//0001&0010  = 0000=0
	int b = 1 | 2;//0001|0010  = 0011=3
	int c = 1 ^ 2;//0001^0010  = 0011=3
	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);
	return 0;
}

赋值操作符

int a = 10;
int b = 20;

  • 还有复合赋值符,+=,-=,*=,/=,%=,>>=,<<=,&=,|=,^=

int a = 10;
a = a+10;
a+=10;//与上条代码是一样的效果,但是更加简洁

单目操作符

! - + & ~ - - ++ *
逻辑取反 负值 正值 取地址 对二进制按位取反 自减 自增 解引用操作符
  • 自增++和自减–运算符分为前置和后置,前置是先加再用,后置是先用再加
#include 
int main()
{
	int a = 1;
	int a1 = 2;
	int b = ++a;
	int c = a1++;
	printf("%d\n", a);//2
	printf("%d\n", b);//2
	printf("%d\n", c);//2
	return 0;
}

关系操作符

> >= < <= != ==
大于 大于等于 小于 小于等于 不等于 等于
逻辑操作符
  • ||逻辑或,&&逻辑与
  • 当多个逻辑或相关运算组合时,若按照顺序若有两个操作数的结果是1,则后面的不再执行
  • 当多个逻辑与相关运算组合时,若按照顺序若有两个操作数的结果是0,则后面的不再执行

1&2的结果是0
1&&2的结果是1
1|2的结果是3
1||2的结果是1

条件操作符

  • exp1?exp2 : exp3
int main()
{
	int a = 1;
	int b = 2;
	int c = a > b ? a : b;
	printf("%d", c);//2
	return 0;
}

逗号表达式

  • 表达式用逗号隔开,从左向右依次执行,整个表达式的结果是最后一个表达式的结果
include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a < 10, a, b = a + 1);
	printf("%d", c);//2
	return 0;
}

结构成员

  • 访问结构体成员
    • 结构体 . 成员名
    • 结构体指针->成员名
#include 
struct book
{//结构体的成员变量
	char name[10];
	int price;
};
int main()
{
//  int         bk =  0;
	struct book bk = { "c语言",50 };
	printf("%s\n", bk.name);//结构体变量名.成员名
	printf("%d\n", bk.price);
	return 0;
}
#include 
struct book
{//结构体的成员变量
	char name[10];
	int price;
};
int main()
{
//  int         bk =  0;
	struct book bk = { "c语言",50 };
	struct book* pbk = &bk;//指向结构体的指针
	printf("%s\n", (*pbk).name);//结构体变量名.成员名
	printf("%d\n", (*pbk).price);
	return 0;
}
#include 
struct book
{//结构体的成员变量
	char name[10];
	int price;
};
int main()
{
//  int         bk =  0;
	struct book bk = { "c语言",50 };
	struct book* pbk = &bk;//指向结构体的指针
	printf("%s\n", pbk->name);//pbk是个结构体指针
	printf("%d\n", pbk->price);//结构体指针->成员名
	return 0;
}

整型提升

  • C的整型算术运算总是至少以缺省整型类型的精度来进行的,为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
  • 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度
  • 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
  • 整型提升是按照变量的数据类型的符号位来进行提升的

char a,b,c;
a=b+c;
b和c的值被提升为普通整型,再执行加法运算,结果将被截断,再存储在a中

#include 
int main()
{
	char a = 1;
	//00000000 00000000 00000000 00000001补码
	//将1交给a时,因为a的类型为char类型即只有一个字节,
	// 所以a中只能储存一个字节即8个比特位,进行截断只保留最后的8个比特位
	//000000001  -a截断后
	char b = 127;
	//00000000 00000000 00000000 01111111补码
	//01111111   -b截断后
	char c = a + b;
	//00000000 00000000 00000000 00000001
	//00000000 00000000 00000000 01111111
	//00000000 00000000 00000000 10000000
	//10000000  截断后再进行整型提升
	//11111111 11111111 11111111 10000000补码
	//11111111 11111111 11111111 01111111反码
	//10000000 00000000 00000000 10000000原码
	printf("%d", c);//-128
	return 0;
}
#include 
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));//1
	printf("%u\n", sizeof(+c));//4
	printf("%u\n", sizeof(-c));//4
	//c只要参与表达式运算, 就会发生整形提升
	return 0;
}

算术转换

  • 如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算,算术转换要合理
  • 如果某个操作数的类型在上面下面列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
  • 但注意不要有符号和无符号的混用
    C语言基础入门学习--学习笔记_第7张图片

操作符的属性

  • 所用变量名用a,b代替,优先级递减,优先级相同,取决于结合性
  • 总之,! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
运算符 描述 使用方法 结合方向 补充
() 圆括号 (表达式)/函数名(形参名) L->R
[ ] 下标引用 数组名[常量表达式]
. 访问结构成员 结构体.成员名
-> 访问指针结构成员 结构体指针->成员名
++ 后缀自增 a++
-- 后缀自减 a--
! 逻辑反 !a R->L
~ 按位取反 ~a
+ 表示正值,单目 +a,+可省略
- 表示负值,单目 -a
++ 前缀自增 ++a
-- 前缀自减 --a
* 间接访问,解引用 *a
& 取地址 &a
sizeof 计算长度,单位字节 sizeof(a)
(类型) 类型转换 (类型)a
* 乘法 a*a L->R
/ 除法 a/a
% 取余 a%a
+ 加法 a+a
- 减法 a-a
<< 左移 a<<1
>> 右移 a>>1
> 大于 a>a
>= 大于等于 a>=a
< 小于 a < a
<= 小于等于 a<=a
== 等于 a==a
!= 不等于 a!=a
& 按位与 a&a
^ 按位异或 a^a
| 按位或 a|a
&& 逻辑与 a&&a
|| 逻辑或 a||a
? : 条件操作符,三目 exp?exp:exp
= 赋值 a=b R->L
+= 以....加 a+=b
-= 以....减 a-=b
*= 以....乘 a*=b
/= 以....除 a/=b
%= 以....取模 a%=b
<<= 以....左移 a<<=b
>>= 以....右移 a>>=b
&= 以....与 a&=b
^= 以....异或 a^=b
|= 以....或 a|=b
, 逗号 a,b

指针

  • 指针是内存中一个最小单元的编号,也就是地址,指针就是指针变量,是用来存放内存地址的变量
  • 可以通过&符号取出变量的内存的起始地址,可以把地址放在一个变量中,这个变量就是指针变量
  • 一个字节给一个对应的地址是比较合适,在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节
#include 
int main()
{
	int a = 1;   //a是变量,a的类型是int(整型)
	int* p = &a; //p是指针变量,p的类型是int*,p里面存放的是a的地址
	printf("%p", &a);//a是4个字节,取到的是a的4个字节中的第一个字节的地址
	return 0;
}

C语言基础入门学习--学习笔记_第8张图片

  • char*类型的指针是为了存放char类型变量的地址
  • short*类型的指针是为了存放short类型变量的地址
  • int*类型的指针是为了存放int类型变量的地址
  • 指针的类型决定了指针进行解引用操作时,能够访问空间的大小,int能够访问4个字节,charp能够访问1个字节,double*能够访问8个字节
  • int*p p+1是跨过4个字节
  • char* p p+1跨过1个字节
    C语言基础入门学习--学习笔记_第9张图片
#include 
int main()
{
	int a = 0x11223344;
	char* pc = &a;
	*pc = 0;
	printf("%p\n", pc);
	return 0;
}

C语言基础入门学习--学习笔记_第10张图片
野指针

  • 野指针是指针指向的位置是不可知的,造成的原因有多种,如指针未初始化、指针越界访问等

  • 如何规避?

    • 指针初始化
    • 注意指针是否越界
    • 指针无确定指向时,可以指向NULL
    • 避免返回局部变量的地址
    • 使用前检查指针的有效性

指针和数组

  • 数组名代表首元素的地址
  • 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
    C语言基础入门学习--学习笔记_第11张图片
    二级指针
    指针变量也是变量,也有地址

int a = 1;
int* pa = &a;
int** ppa = &pa;
a的地址存放在pa中,pa的地址放在ppa中
pa是一级指针,ppa是二级指针

字符指针

#include 
int main()
{
	char a = 'a';
	char* pc = &a;
	*pc = 'b';
	printf("%c %c", a, *pc);//b b
	return 0;
}

p储存的只是字符串首元素的地址,%s需要的是指针变量,会一直向后打印直到遇到\0,由于存的是首元素的地址,故解引用时,只会打印a

#include 
int main()
{
	char* p = "abcd";//字符串常量,不能被修改,最好加const进行修饰
	printf("%c ", *p);//a
	printf("%s ", p);//abcd
	return 0;
}

指针数组

  • 指针数组是数组,是用来存放指针的

int main()
{
int arr[5] = { 0 };//整型数组
char ch[5] = { 0 };//字符数组
int* parr[3];//存放整型指针的数组-指针数组
char* pch[2];//存放字符指针的数组=字符数组
//[]的优先级高于*
return 0;
}

#include 
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[3] = { &a,&b,&c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));//1 2 3
	}
	return 0;
}
#include 
int main()
{
	int arr1[3] = { 1,2,3 };
	int arr2[3] = { 4,5,6 };
	int arr3[3] = { 7,8,9 };
	int* arr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("%d ", *(arr[i] + j));//arr[i]是某个数组首元素的地址,+j是该数组的其后元素的地址,最后解引用求出该值,
		}                                //也可以写成arr[i][j]
		printf("\n");
	}
	return 0;
}

数组指针
数组指针是指向数组的指针,是用来存放数组的地址

int main()
{
int* p = NULL;//p是整型指针-指向整型的指针-可以存放整型的地址
char* pc = NULL;//pc是字符指针-指向字符的指针-可以存放字符的地址
int arr[10] = { 0 };
//arr-首元素的地址
//&arr[0]-首元素的地址
//&arr-数组的地址
//整型指针是用来存放整型的地址
//字符指针是用来存放字符的地址
//数组指针是用来存放数组的地址
int arr1[3]={1,2,3};//这个数组3个元素,每个元素是个整型
int (*parr1)[3]=&arr1;//数组的地址要存起来,要用数组指针
//parr1就是数组指针,指向了一个数组,这个数组3个元素,每个元素是个整型
return 0;
}

int main()
{
	char* arr[6];
	char* (*pa)[6] = &arr;//pa是数组的地址,用数组指针存储
	//指针pa指向的是数组arr,
	//pa指向的数组arr是6个元素,每个元素是类型char*
	return 0;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//这种用法比数组指针方法简单
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	
	//一般不这么用数组指针,比较麻烦,指针数组一般用于二维数组
	//int(*pa)[10] = &arr;
	//int i = 0;
	//for (i = 0; i < 10; i++)
	//{
	//	printf("%d ",(*pa)[i]);//对数组的地址进行解引用得到的是整个数组
	                           int* p=&a;,p存储的是a的地址,对p进行解引用*p,得到是a,类比
	                                    pa存储的是数组arr的地址,对pa进行解引用*pa,得到的是arr
	//	//printf("%d", *(*pa + i));// *p==arr即*(arr+i)
	//}
	return 0;
}
#include 
void print(int(*p)[3], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			printf("%d ", *(*(p + i) + j));//*(p+i)是某一行的地址
		}                  //p[i][j]
	}
}                 //p+i是第i行的地址,*(p+i)就是对这一行的地址解引用==p[i]==这一行的数组名,即首元素的地址
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	print(arr, 3, 3);
	return 0;
}

函数指针

  • 函数指针是指向函数的指针,是存放函数地址的一个指针
  • &函数名和函数名都是函数的地址
#include 
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 1;
	int b = 2;
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

int(*pa)(int, int),去掉pa就是指针的类型,去掉(*pa)就是指针指向元素的类型

#include 
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 1;
	int b = 2;
	int(*pa)(int, int) = Add;//pa存的是函数的地址,*pa找到这个函数
	printf("%d ", (*pa)(1, 2));//3....*可有可无, &函数名和函数名都是函数的地址,调用可以直接Add(1,2)==pa(1,2)
	printf("%d ", pa(1,2));//3
	return 0;
}
//signal是一个函数声明
//signal函数的参数有2个,第一个是int,第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
//signal函数的返回类型也是一个函数指针,该函数指针指向的函数的参数是int,返回类型是void
	void (*signal(int, void(*)(int)))(int);
	//该函数signal的返回类型是void (*)(int)-太复杂
	typedef void(*p_sig)(int);//将void (*)(int)重新定义为p_sig
	p_sig signal(int, p_sig);
	//这两句等同于第一句

函数指针数组

  • 函数指针数组就是把函数的地址存到一个数组中
#include 
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
int main()
{
	int(*parr[4])(int, int) = { Add,Sub,Mul,Div };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n", parr[i](1, 2));//3 -1 2 0
	}
	return 0;
}

指向函数指针数组的指针

  • 指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素是函数指针

int arr[10] = { 0 };
int(*p)[10] = &arr;//取出数组的地址,存到数组指针
int(*pf)(int, int);//函数指针
int (pfarr[4])(int, int);//pfarr是个数组,是函数指针的数组
int (
(ppfarr)[4])(int, int)=&pfarr;
//ppfarr是一个数组指针,指针指向的数组有4个元素
//指向的数组的每个元素的类型是一个函数指针,类型是int(
)(int ,int)

回调函数

  • 回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这就是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

结构体

  • 结构体是一些相同或不同值的集合,这些值成为成员变量,结构体的成员可以是标量、数组、指针、甚至是其他结构体
    结构体声明和初始化

struct book
{
char name[20];
int price;
};
int main()
{
struct book bk = { “c语言”,20 };
return 0;
}
或者
typedef struct book
{
char name[20];
int price;
}bk1;
int main()
{
bk1 b1 = { “c语言”,20 };
return 0;
}

结构体成员的访问

  • 结构体成员的访问是通过操作符.进行访问的
#include 
typedef struct book
{
	char name[20];
	int price;
	int x;
	double y;
}bk1;
struct s
{
	int a;
	bk1 b;
	char *pc;
};
int main()
{
	char arr[] = "hello";
	struct s s1 = { 1,{"张三",20,2,1.0},arr };
	printf("%d\n", s1.a);
	printf("%s\n", s1.b.name);
	printf("%s\n", s1.pc);
	return 0;
}

结构体传参

#include 
typedef struct book
{
	char name[20];
	int price;
	double y;
}bk1;

void Print(bk1* tmp)
{
	printf("%s\n", tmp->name);
	printf("%d\n", tmp->price);
	printf("%f\n", tmp->y);
}
int main()
{
	bk1 b = { "张三",1,2.0 };
//	Print(b);
	Print(&b);//这种方式好函数传参的时候,参数是需要压栈的。
	//如果传递一个结构体对象的时候,结构体过大,
	//参数压栈的的系统开销比较大,所以会导致性能的下降。
	return 0;

文章难免有些错误,请大家指正!共同进步!!!

你可能感兴趣的:(C语言,c语言,开发语言,后端)