C/C++学习笔记(可以从零开始学习的笔记)

目录

 

目录

C语言基础

介绍

变量

常量

转义字符

定义符号常量

标识符

字符串常量

数据类型

sizeof运算符

signed和unsigned

字符

字符串

运算符

循环条件结构

拾遗

数组

字符数组

字符串处理函数

二维数组

指针

避免访问未初始化的指针

指针和数组

指向数组的指针

指针数组和数组指针

二维数组

void指针

NULL指针

指向指针的指针

指针数组与指向指针的指针

常量和指针

指向非常量的常量指针

函数

参数与指针

可变参数

指针函数与函数指针

函数指针:指向函数的指针

局部变量与全局变量

作用域与链接属性

生存期和存储类型

动态内存管理

宏定义

结构体

单链表

typedef

共用体

枚举类型

C++特性

命名空间的使用(嵌套,合并,以及命名污染)

函数小tips

函数重载

基础知识:



C语言基础

介绍

C语言=>汇编语言=>机器语言=>cpu执行

java=>字节码=>解释器=>cpu执行

#include <stdio.h>

int main()
{
	printf("I love fishC.com\n");
	print\
f("\n\
	   *\n\
	  ***\n\
	 *****\n\
	*******\n");
	return 0;
}

变量

变量名:

只能用英文字母和数字或者下划线组成

第一个字符必须是字母或者下划线

变量名区分大小写

#include <stdio.h>

int main()
{
	int a;
	char b;
	float c;
	double d;
	
	a = 520;
	b = 'F';
	c = 3.14;
	d = 3.141592653;
	
	printf("小红帽公司创办于2010年的%d\n",a);
	printf("I love %cishC.com!\n",b);
	printf("圆周率是:%.2f\n",c);//%。2f指精确到小数点后两位的f浮点数 
	printf("精确到小数点后9位的圆周率是:%11.9f\n",d);//%11.9f指精确到小数点后九位,且占位宽度位11的f浮点数 
	
	return 0;
}

字符型:'a'

整型:520

单精度浮点型:float

双精度浮点型:double

常量

整型常量:520,1314

实型常量:3.14,5.12

字符常量

-普通字符:'L','o','v','e'

转义字符

\a 响铃bell

\b 退格

\f 换页

\n 换行

\r 回车

\t 水平制表,跳到下一个tab位置

\v 垂直制表

\\ 表示\本身

\' 表示单引号

\" 表示双引号

\? 表示问号

\0 表示空字符null

\ddd 1到3位八进制数所代表的任意字符

\xhh 1到2位十六进制数所代表的任意字符

\ 表示本行延续至下一行

%d 整型

%f 浮点型

%c 字符型

%s 字符串型

%u 无符号unsigned型

%p 地址

定义符号常量

#define 标识符 常量

#include <stdio.h>

#define URL "http://www.baidu.com"
#define NAME "百度"
#define BOSS "喝饱水"
#define YEAR 2020
#define MONTH 5
#define DAY 20

int main()
{
	printf("%s成立于%d年%d月%d日\n",NAME,YEAR,MONTH,DAY);
	printf("%s是%s创立的……\n",NAME,BOSS);
	printf("%s的域名是%s\n",NAME,URL);
	return 0; 
}

标识符

只能由英文字母,数字或下划线组成

首字符必须是字面或下划线

区分大小写,不能用关键字来命名标识符

字符串常量

"Hello World"\\实际上在字符串最后还追加了一个\0来追加一个空字符,代表该字符串结束

数据类型

基本类型:整型(short int,int,long int,long long int),浮点型(float,double,long double),字符类型(char),布尔类型(_Bool),枚举类型(enum)

指针类型

构造类型:数组类型,结构类型,联合类型

空类型

sizeof运算符

可以用于获得数据类型或表达式的长度;

-sizeof(object); //sizeof(对象);

-sizeof(type_name); //sizeof(类型);

-sizeof object; //sizeof 对象;

#include <stdio.h>

int main()
{
	int i;
	char j;
	float k;
	
	i = 123;
	j = 'C';
	k = 3.14;
	
	printf("size of int is %d\n",sizeof(int));
	printf("size of i is %d\n",sizeof(i));
	printf("size of char is %d\n",sizeof(char));
	printf("size of j is %d\n",sizeof(j));
	printf("size of float is %d\n",sizeof(float));
	printf("size of k is %d\n",sizeof(k));
	
	return 0;
}

signed和unsigned

[signed] short [int]:可以存放负数

unsigned short [int]:不能存放负数

#include <stdio.h>

int main()
{
	short i;//默认带符号位 
	unsigned short j;
	
	i = -1;
	j = -1;
	
	printf("%d\n",i);
	printf("%d\n",j);
	
	return 0;
}

字节-比特位:1Byte == 8bit

#include <stdio.h>
#include <math.h>

int main()
{
	int result = pow(2,32) - 1;//对于32位的整型变量,在signed类型的存储单元中,左边第一位表示符号位,所以表示值的只有31个bit
	unsigned int result1 = pow(2,32) - 1;//无符号位的可以表示32个bit 
	printf("result = %d\n",result);
	printf("result1 = %u\n",result1);
	
	return 0;
}

基本数据类型的取值范围:

数据类型字节数取值范围
char1-128~127
unsigned char10~255
short2-32768~32767
unsigned short20~65535
int4
unsigned int4
long4
unsigned long4
long long8
unsigned long long8
float4
double8
long double12

字符

#include <stdio.h>

int main()
{
	char a ='C';
	unsigned char height = 170;//必须用unsigned,这时候取值范围才对 
	printf("%c = %d\n",a,a);//%d对应的是C的ASCII码,字符类型就是一个特殊的整型 
	printf("阿大的身高是%d\n",height);
	
	return 0;
}

字符串

char 变量名[数值];

#include <stdio.h>

int main()
{
	char a[6] = {'F','i','s','h','C','\0'};//尾部加上\0代表字符串结束 ,也可以直接用双引号引出字符串常量"FishC",不用加\0 
	printf("%c\n",a[1]);//可以对字符串提取其索引,获得对应位置上的字符,此处是i 
	printf("%s\n",a);
	printf("Hello\n");
	
	return 0;
}

运算符

算数运算符(高):

运算符名称例子结果
+加法(双目)
-
*
/
%
+正号(单目)
-

目:

1+2(操作数 运算符 操作数),有几个操作数就是几目

表达式:用运算符和括号将操作数连接起来的式子

1+2

'a'+'b'

a+b

#include <stdio.h>
#include <math.h>


int main()
{
	int i,j,k;
	i = 1 + 2;
	j = 1 + 2 * 3;
	k = i + j + -1 + pow(2,3);//3 + 7 +(-1)+8=17
	
	printf("i = %d\n",i);
	printf("j = %d\n",j);
	printf("k = %d\n",k);
	
	return 0;
}

类型转换:操作数类型不同时,占位少的转换成占位多的,如下:

1+2.0 == 1.0+2.0 整型转换成浮点型

#include <stdio.h>

int main()
{
	
	printf("整数输出:%d\n",1 + 2.0);//整型就应该转换成浮点型去输出,用%d强制转换会使其报错 
	printf("浮点数输出:%f\n",1+2.0);//此处就不会报错,输出3.0 
	printf("整数输出:%d\n",1 + (int)2.2);//此处可以将2.2强制转换为整型再参加运算,转换为int型的时候,不是进行四舍五入,而是直接去除小数位 
	
	return 0;
}

关系运算符(中):

优先级相同(高):

        <(小于)

        <=(小于等于)

        >(大于)

        >=(大于等于)

优先级相同(低):

        ==(等于)

        !=(不等于)

关系表达式:只会返回一个逻辑值,1or0,表示True or False

用关系运算符将两边的变量、数据或表达式连接起来,称为关系表达式

1

a>b

a'a'+'b'

(a=3)>(b=5)

#include <stdio.h>

int main()
{
	int a =5,b = 3;
	
	printf("%d\n",1 < 2);
	printf("%d\n",a > b);
	printf("%d\n",a <= b + 1);
	printf("%d\n",'a' + 'b' <= 'c');
	printf("%d\n",(a = 3) > (b =5));
	
	return 0;
}

逻辑运算符(低):

运算符含义优先级举例说明
!逻辑非
&&逻辑与
||逻辑或

逻辑表达式:用逻辑运算符将两边的变量、数据或表达式连接起来,称为逻辑表达式

3>1 && 1

3+1 || 2==0

!(a+b)

!0 + 1 < 1 || !(3+4)

'a' - 'b' && 'c'

#include <stdio.h>

int main()
{
	int a =5 ,b = 3;
	
	printf("%d\n",3 > 1 && 1 < 2);
	printf("%d\n",3 + 1 || 2 == 0);
	printf("%d\n",!(a + b));
	printf("%d\n",!0 + 1 < 1 || !(3 + 4));//!0 => 1 + 1 => 2 < 1 => 0 || !7 => 0 || 0 => 0
	printf("%d\n",'a' - 'b' && 'c');
	
	(a = 0) && (b = 5);
	printf("a = %d,b = %d\n",a,b);//从a的值的大小就可以给出逻辑运算结果,所以不需要进行后面的运算了,故b=5不会执行,b仍为3 
	
	(a = 1) || (b = 5);
	printf("a = %d,b = %d\n",a,b);//称为短路求值 
	
	return 0;
}

优先级与结合性表

循环条件结构

if语句结构

……//其他语句

if (表达式1)

{

    ……//表达式1的逻辑值为真所执行的语句、程序块

}

else if (表达式2)

{

    ……//表达式2的逻辑值为真所执行的语句、程序块

}

else

{

    ……//逻辑值都为假所执行的语句、程序块

}

……//其他语句

#include <stdio.h>

int main()
{
	int i;
	printf("您老贵庚啊:");
	scanf("%d",&i);
	
	if (i >= 18)
	{
		printf("进门左拐!\n"); //{}把几条语句组成一个程序块 
	}
	else if (i < 18 && i >= 10)
	{
		printf("进门右拐!\n");
	}
	else
	{
		printf("慢走不送!\n");
	}
	
	return 0;
}

 switch语句结构

……//其他语句

switch (表达式)

{

    case 常量表达式1:语句或程序块1

    case 常量表达式2:语句或程序块2

    ……

    case 常量表达式n:语句或程序块n

    default:语句或程序块n+1

}

……//其他语句

#include <stdio.h>

int main()
{
	char ch;
	
	printf("请输入成绩:");
	scanf("%c",&ch);//输入ch的值
	
	switch (ch)
	{
		case 'A': printf("你的成绩在90分以上!\n"); break;//如果是A,就执行后面的语句 
		case 'B': printf("你的成绩在80~90分之间!\n"); break;
		case 'C': printf("你的成绩在70~80分之间!\n"); break;
		case 'D': printf("你的成绩在60~70分之间!\n"); break;
		case 'E': printf("你的成绩在60分以下!\n"); break;
		default: printf("请输入有效的成绩!\n"); break;//如果以上都不是,就执行后面的语句 
	}
	
	return 0;
}

嵌套结构:

#include <stdio.h>

int main()
{
	int a,b;
	
	printf("请输入两个数:");
	scanf("%d %d",&a,&b);//输入值时,格式为23 32 
	
	if (a !=  b)
	{
		if (a > b)
		{
			printf("%d > %d\n",a,b);
			
		}
		else
		{
			printf("%d < %d\n",a,b);
		}
	}
	else
	{
		printf("%d = %d\n",a,b);
	}
	
	return 0;
}

悬挂else:

#include <stdio.h>

int main()
{
	char isRain,isFree;
	
	printf("是否有空?(Y/N)\n");
	scanf("%c",&isFree);
	
	getchar();//把回车输入进getchar()中去 ,否则这个回车会被下面的scanf获取 
	
	printf("是否下雨?(Y/N)\n");
	scanf("%c",&isRain);
	
	if (isFree == 'Y')
 	{
		if (isRain == 'Y')//此处没有放置{},也执行成功了
  		{
			printf("记得带伞哦!\n");
   	}
   }
	else
	{
		printf("女神没空~\n");
	}
	
	return 0;
}

while循环结构

入口条件循环:

while (表达式)

    循环体

出口条件循环:

do

    循环体

while (表达式);

#include <stdio.h>

int main()
{
	int count = 0;//初始化计数器
	
	printf("输入字符:"); 
	while (getchar() != '\n')//循环条件:getchar != '\n'时,执行以下循环体,直到输入回车即停止
	{
		count = count + 1;//更新计数器
	}
	
	printf("你总共输入了%d个字符!\n",count);
	
	return 0;
}

for循环与break的应用

for (表达式1;表达式2;表达式3)//表达式可以省略,;不能省略

//暴力求解素数
#include <stdio.h>

int main()
{
	int i,num;
	bool flag = 1;
	
	printf("请输入一个整数:");
	scanf("%d",&num);
	
	for (i = 2;i < num / 2;i++)
	{
		if (num % i == 0)
		{
			flag = 0;
			break;//如果没有break,本循环会一直执行下去,直到跳出循环条件为止,加了break后,只要执行一次本表达式就会自动跳出循环,减少资源消耗 
		}
	}
	if (flag)
	{
		printf("%d是一个素数!\n",num);
	}
	else
	{
		printf("%d不是一个素数\n",num);
	}
	
	printf("i = %d\n",i);//输入100000,没有break时,i=50000,有break时,i=2 
	return 0;
}

循环嵌套:

#include <stdio.h>

int main()
{
	int i,j;
	
	for (i = 1;i <= 9;i++)
	{
		for (j = 1;j <= i;j++)
		{
			printf("%d*%d=%-2d  ",i,j,i*j);//%-2d中的2指补足数据到2位,-表示数据左对齐 
		}
		putchar('\n');
	}
	
	return 0;
 } 

break与continue语句:

//printf在不同位置的输出效果不同以及break的作用 
#include <stdio.h>

int main()

{
	int i,j;
	for (i = 0;i < 10;i++)
	{
		for (j = 0;j < 10;j++)
		{
			if (j == 3)
			{
				break;//注意break只能跳到一层循环
			}
		printf("i = %d;j = %d \n",i,j);
		}
	//printf("i = %d;j = %d \n",i,j);
	}
	//printf("i = %d;j = %d \n",i,j);
	
	return 0;
}

--------分割线----------

//continue的用法
#include <stdio.h>

int main()
{
	int i;
	for (i = 0;i < 10;i++)
	{
		if (i % 2)
		{
			continue;//对于for来说,continue后,直接到i++ 
		}
	}
	
	/*while (i < 10)
	{
		if (i % 2)
		{
			continue;//这个continue会自动跳过i++,进行下一次循环,导致i无法自增,陷入死循环 
		}
		i++;
	}*/
	
	printf("i = %d \n",i);
	
	return 0;
}

拾遗

复合赋值运算符:

a = a + 1 a += 1(+-*/%均可)

自增自减运算符:

a++与++a

a--与--a

逗号运算符:

i=1;

j=2; => i=1,j=2;

若a = 3,5;则结果为

a = 3;

5;

注意逗号运算符优先级很低,甚至低于赋值运算符,且其运算顺序是从左到右。

#include <stdio.h>

int main()
{
	int i = 5,j;
	
	j = ++i;//先计算再赋值,++i,i=6,j=i,j=6
	printf("i = %d,j = %d\n",i,j);
	
	i = 5;
	j = i++;//先赋值再计算,j=i,j=5,i++,i=6
	printf("i = %d,j = %d\n",i,j);
	
   a = 3,5;//注意,此时a = 3;
	printf("a = %d",a);
 
	a = (3,5);
	printf("a = %d",a);//在括号里的值,选择最后的那个值赋给变量a,a=5
                                                                                                          
	return 0;
}

条件运算符:

exp1?exp2:exp3;

exp1是条件表达式

如果结果为真,返回exp2

如果结果为假,返回exp3

if (a > b)

    max = a;

else

    max = b;

<=>

max = a > b ? a : b;

goto语句

#include <stdio.h>

int main()
{
	int i = 5;
	
	while (i++)
	{
		if (i > 10)
		{
			goto A;//直接跳到A处 
		}
	}

A:	printf("Here,i = %d\n",i);

	return 0;
}

数组

数组定义:

类型 数组名[元素个数];

int a[6];//一个int是4字节,4*6=24字节

char [24];//一个char是1字节,1*24=24字节

double c[3];//一个double是8字节,8*3=24字节

数组不能动态定义

注意不能这样:int a[n];//[]中必须都是常量,不能是变量n,现在的C99可以支持

数组[元素个数];

int a[5];//定义一个有5个元素的数组

a[0];//访问a数组中的第一个元素

a[4];//访问a数组中的第五个元素

a[5];//报错,超出数组范围

//使用循环访问数组
int a[10];

for (i = 0;i < 10;i++)
{
	a[i] = i;
 } 

------分割线-------

#include <stdio.h>

#define NUM 10//宏定义NUM = 10 

int main()
{
	int s[NUM];
	int i,sum = 0;//初始化数值 
	for (i = 0;i < 10; i++)
	{
		printf("请输入第%i位同学的成绩:",i + 1);//i+1代表在数组中的位数 
		scanf("%d",&s[i]);//向数组中填入第i个数值 
		sum += s[i];//sum开始递增 
		
	}
	
	printf("成绩录入完毕,该次考试的平均分是:%.2f\n",(double)sum / NUM);//计算出平均值 
	
	return 0;
}

数组初始化

int a[10] = {1,2};//第一个数为1,第二个数为2,其余数均为0

int a[10] = {1,2,3,4,5,6,7,8,9,10};

int a[] = {1,2,3,4,5,6,7,8,9,0};//可以自动判断出a数组的长度并填充,这里可以偷懒

C99新特性:int a[10] = {[3] = 3}

#include <stdio.h>

int main()
{
	int a[10] = {[3] = 3,[5] = 5,[8] = 8};//C99
	int i;
	
	for (i = 0;i < 10;i++)
	{
		printf("%d\n",a[i]);
	}
	
	printf("%d\n",sizeof(a));//sizeof求数组a所占用的内存 
	
	return 0;
}

------分割线------

//一个小demo
#include <stdio.h>

int main()
{
	int n,i;
	
	printf("请输入字符的个数:\n");
	scanf("%d",&n);
	
	char a[n+1];//共n+1个元素,包括结尾的\0
	
	printf("请开始输入字符:\n");
	getchar();//把上面的换行符吸收,并释放掉 
	for (i = 0;i < n;i++)//本循环中,共填充了n个元素
	{
		scanf("%c",&a[i]);
	}
	
	a[n] = '\0';//a[n]就是第n+1个元素,作为字符串的结尾,以\0结束 
	printf("你输入的字符串是:%s\n",a);
	
	return 0;
}

字符数组

char str1[10] = {'F','i','s','h','C','\0'};//初始化字符数组的每个元素

char str1[] = {'F','i','s','h','C','\0'};//不写元素个数也可以

char str1[] = {"FishC"};//使用字符串常量初始化字符数组,{}也可以省略

字符串处理函数

strlen:获取字符串长度

#include <stdio.h>
#include <string.h>

int main()
{
	char str[] = "I love FishC.com!";
	
	printf("sizeof str = %d\n",sizeof(str));//指的是字符串中的尺寸,包含结束符\0 
	printf("strlen str = %u\n",strlen(str));//strlen的返回值是unsigned int ,指的是字符串的长度,不包含结束符\0 
	
	return 0;
}

strcpy和strncpy:拷贝字符串

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "Original String";
	char str2[] = "New String";
	char str3[100];
	char str4[] = "to be or not to be";
	char str5[40];
	
	strcpy(str1,str2);//将str2拷贝到str1上,且将str2的结束符\0也拷贝过去了,提示其按点结束 ,str1的长度必须足以容纳str2,否则会溢出 
	strcpy(str3,"Copy Successful");
	strncpy(str5,str4,5);//限制拷贝字符数为5,但这5个字符中不包含结束符 
	str5[5] = '\0';//加上结束符 
	 
	printf("str1:%s\n",str1);
	printf("str2:%s\n",str2);
	printf("str3:%s\n",str3);
	printf("str5:%s\n",str5);
	
	return 0;
}

strcat与strncat:连接字符串

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "I love";
	char str2[] = "FishC.com!";

	
	strcat(str1," ");//将str1与空格连接 
	strcat(str1,str2);//再将其与str2连接 
	//strncat也可以限制拷贝字符数,且会自动追加结束符
	printf("str1:%s\n",str1);
	
	return 0;
}

strcmp与strncmp:比较字符串

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "Love!";
	char str2[] = "Love!";

	if (!strcmp(str1,str2))//str1与str2的ASCII码完全相等,则为0,若str1的ASCII码大
 //返回大于0的值,若str2的ASCII码大,返回小于0的值
	{
		printf("两个字符串完全一致\n");
	}
	else
	{
		printf("两个字符串存在差异\n");
	}

	return 0;
}

二维数组

类型 数组名[常量表达式][常量表达式]

int a[6][6];//6*6,6行6列

char b[4][5];//4*5,4行5列

a[0][0];//访问a数组的第一行第一列的元素

//用二维数组实现矩阵转置 

#include <stdio.h>

int main()
{
	int a[3][4] = {
					{1,2,3,4},
					{5,6,7,8},
					{9,10,11,12}
					};//第一维度可以不写,其他维度不行,比如a[][4]可以计算出来,a[3][]不行 
	int i,j;
	
	//int b[3][4] = {[0][0] = 1,[1][1] = 2,[2][2] = 3};C99新特性 ,可以按索引赋值,其他位置默认为0 
	
	//printf(b[3][4]);
	
	for (i = 0; i < 3; i++)
	{
		
		for (j = 0; j < 4; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}

	for (i = 0; i < 4; i++)
	{
		
		for (j = 0; j < 3; j++)
		{
			printf("%d ", a[j][i]);
		}
		printf("\n");
	}//获得上矩阵的转置 
	
	
	return 0;
}

指针

根据存储的值的地址,来找到对应的值

变量的地址

存放的值

10000

'H'

10001

'i'

10002

's'

10003(d变量的地址在这里,并按其类型占4个字节来向下获取3个字节)

123

10004

10005

10006

左边是内存地址,地址就是一个索引,右边是对应内存地址上的存储的值,比如char型变量占用一个字节,也就是占用一个地址,int型变量占用四个字节,也就是占用四个地址。

注意:在内存中完全没有必要存放变量名

因为变量名是为了方便使用而定义的,只有人和编译器知道,当你访问某个变量的时候,编译器会根据变量所在的地址以及变量的类型,来访问特定范围内的数据

只有编译器知道

变量

变量的地址

a

10000

b

10001

c

10002

实际上也就是访问到变量所在的地址,再根据变量类型,确定其占多少个字节

d

10003

指针和指针变量:指针变量的地址里存着一般变量的地址,这个指针变量的地址叫指针

指针变量

指针变量的地址

pa

11000

指针变量的地址(指针)

存放的值(一般变量的地址)

11000(这4个字节称为一个指针,可以存放一个地址)

10000(比如这里存放的就是变量a的地址)

11001

11002

11003

根据10000这个地址,就可以获取到一般变量的地址与存放的值了

定义指针变量:

类型名 *指针变量名

char *pa;//定义一个指向字符型的指针变量,意思是指向a这个字符型变量

int *pb;//定义一个指向整型的指针变量

取址运算符与取值运算符:

若要获取某个变量的地址,可以使用取地址运算符(&):

char *pa = &a;

若要访问指针变量指向的数据,可以使用取值运算符(*):

printf("%c,%d\n",*pa,*pb);

#include <stdio.h>

int main()
{
	
	char a = 'F';
	int f = 123;
	
	char *pa = &a;//这里的*是指定义指针变量,&是取址运算符 
	int *pb = &f;
	
	*pa = 'C';//这里的*pa就是指a的值,因为这里的*是取值运算符,此时就是利用取值运算符来对a进行间接的赋值 
	*pb += 1;
	
	printf("a = %c\n",*pa);//这里的*也是取值运算符 
	printf("f = %d\n",*pb);
	
	printf("sizeof pa = %d\n",sizeof(pa));//32位输出是4,意思是pa存储的是变量a的地址,占4个字节,但64位输出是8,原因不详 
	printf("sizeof pb = %d\n",sizeof(pb));
	
	printf("the addr 0f a is: %p\n",pa);//%p指的是地址类型的数据,pa是指针变量,其取值是a的地址,也可以直接使用&a来寻址 
	printf("the addr of f is: %p\n",pb);
		
	return 0;
}

避免访问未初始化的指针

#include <stdio.h>

int main()
{
	
	int *a;//没有初始化的指针,就是野指针,它在栈里的地址是随机分配的,但又是合法的 
	
	*a = 123;//此时相当于向一个随机的地址进行赋值,可能会覆盖到系统的其他代码,此时系统会强制终止,若没有覆盖其他代码且赋值成功,出现问题也会非常难以排查,因为地址是随机分配的 
		
	return 0;
}

指针和数组

#include <stdio.h>

int main()
{
	int a;
	int *p = &a;//将a的地址赋值给指针变量p 
	
	printf("请输入一个整数:");
	scanf("%d",&a);//给a传入具体的值 
	printf("a = %d\n",a);
	
	printf("请重新输入一个整数:");
	scanf("%d",p);//p与&a类似 
	printf("a = %d\n",a);//这里输出的就是a的值
	
	return 0;
}

定义数组

#include <stdio.h>

int main()
{
	char str[128];//定义数组str 
	
	printf("请输入阿大的域名:");
	scanf("%s",str);
	
	//printf("阿大的域名是: %s\n",str);
	
	printf("阿大的域名是: %p\n",str);//数组名是数组第一个元素的地址,实际上指针也就是存储了某个变量的第一个地址 
	printf("阿大的域名是: %p\n",&str[0]);//如果一个变量存储的是另一个变量的地址叫指针,如果一个变量存储的是这个变量本身的地址叫数组
	//数组名 = 数组的第一个元素的地址 = 数组的地址
	return 0;
}

指向数组的指针

当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素。

#include <stdio.h>
#include <string.h>


int main()
{
	char a[] = "You";
	int b[5] = {1,2,3,4,5};
	float c[5] = {1.1,2.2,3.3,4.4,5.5};
	double d[5] = {1.1,2.2,3.3,4.4,5.5};
	char *str = "I love You!";//定义字符指针变量 
	int i,length;
	
	length = strlen(str);
	
	for (i = 0;i < length;i++)
	{
		printf("%c",str[i]);//说明用数组的下标法也能访问指针变量 
		
	 } 
	printf("\n");
	
	char *p = a;//该指针p指向的是第一个地址,也就是字符串的第一个字节处,此处的值是F 
	
	printf("*p = %c,*(p+1) = %c,*(p+2) = %c\n",*p,*(p+1),*(p+2));//在此基础上,进行指针运算,使其偏移,p+1对应的位置在后面1个字节处,即取值为i处 
	printf("*b = %d,*(b+1) = %d,*(b+2) = %d\n",*b,*(b+1),*(b+2));//数组像一种特殊的指针,所以直接对数组用指针法访问元素,竟也能成功 
	/*
	printf("a[0] -> %p,a[1] -> %p,a[2] = %p\n",&a[0],&a[1],&a[2]);//dev c中地址以00开头,但也是16进制,含义与0x一样,原因不详	
	printf("b[0] -> %p,b[1] -> %p,b[2] = %p\n",&b[0],&b[1],&b[2]);//显然可以看出char是一个字节,float与int是4字节,而double是8字节 
	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;
}

注意:p+1并不是简单地将地址加1,而是指向数组的下一个元素,比如int型就会跳过4个字节,到达下一个元素处。

#include <stdio.h>
#include <string.h>


int main()
{
	char str[] = "I love You!";
	char *target = str;
	int count = 0;
	
	while (*target++ != '\0')//str是数组的第一元素的地址,它不可改变,换成指针变量target后,就可以改变了,而左值必须是一个变量 
	{
		count++;
	}
	
	printf("总共有%d个字符!\n",count);//最后 
	
	return 0;
}
//左值lvalue指用于识别或定位一个存储位置的标识符,且是可改变的,可以把左值理解成变量,右值理解为常量 

注意:数组名只是一个地址,而指针是一个左值。

指针数组和数组指针

int *p1[5];//指针数组,显然[]运算符比*运算符优先级高,所以先被定义为数组,但又有*,指的是数组的每个元素都存放一个指针变量

#include <stdio.h>
#include <string.h>


int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	int e = 5;
	int *p1[5] = {&a,&b,&c,&d,&e};//将地址传给指针变量 
	int i;
	
	for (i = 0;i < 5;i++)
	{
		printf("%d\n",*p1[i]);//把指针变量的值输出,这里的*是取值符 
	}
	
	return 0;
}

-------分割线-------

#include <stdio.h>
#include <string.h>


int main()
{
	char *p1[5] = {"01.Hadoop","02.Zookeeper","03.Hive","04.Kafka","05.Flink"};//数组中的每个元素都是指针变量,指向的是一个字符数组,该指针存放的就是该字符数组首个字符的地址 
	int i;
	
	for (i = 0;i < 5;i++)
	{
		printf("%s\n",p1[i]);//当用%s时,取出的就是该地址及其后面所有字符,当用%d时,取出的是首字符所在的地址,当用*p1[i]时,取出的是首字符。 
	}
	
	return 0;
}

int (*p2)[5];//数组指针,()与[]优先顺序一样,但是结合性是从左到右,所以它先被定义为指针,再成为数组形式。

数组指针是一个指针,它指向的是一个数组,指针数组中的元素指针,指向的其实是数组的第一个元素的地址,这之间有很大的区别。

#include <stdio.h>
#include <string.h>


int main()
{
	int temp[5] = {1,2,3,4,5};//定义一个数组 
	int (*p2)[5] = &temp;//将该数组首元素地址的地址&temp传给数组指针,不能直接写出数组名temp(数组名=数组的第一个元素的地址)
	/*
	(*p2)[5]指的是指向数组的指针,所以这是一个指针,应该赋值给这个指针变量一个数组地址,也就是&temp(取址符加数组名),同时数组名=数组首元素地址,所以也就是数组首元素地址的地址	
	*/
	int i;
	
	for (i = 0;i < 5;i++)
	{
		printf("%d\n",*(*p2 + i));//p2指向的是数组temp的地址即数组首元素地址,*p2就是p2取值,即得到temp数组首元素地址,*(*p+i)就是temp首元素后第i个元素地址上的值,也就是首元素后的第i个元素 
	}
	
	return 0;
}

二维数组

#include <stdio.h>

 int main()
{
	int array[4][5] = {0};//array[4][5]是指向包含5个元素的数组的指针 
	
	printf("sizeof int: %d\n",sizeof(int));
	printf("array: %p\n",array);//array默认为是数组array[0]的地址 
	printf("array+1: %p\n",array + 1);//array+1可以看成array[1]的地址,也就是第二行数组首元素的地址(数组名=数组的地址=数组首元素的地址) 
	
	return 0;
}

array[4][5]的存储依然是在一列中的,但是为方便理解,写成如下形式,

array[0][0]

array[0][1]

array[0][2]

array[0][3]

array[0][4]

array[1][0]

array[1][1]

array[1][2]

array[1][3]

array[1][4]

array[2][0]

array[2][1]

array[2][2]

array[2][3]

array[2][4]

array[3][0]

array[3][1]

array[3][2]

array[3][3]

array[3][4]

#include <stdio.h>


int main()
{
	int array[4][5] = {0};//array[4][5]是指向包含5个元素的数组的指针 
	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进行解引用,%p就是取array的第二行数组的首元素地址 
	printf("array[1]:%p\n",array[1]);//用%p可以取array的第二行数组的首元素地址 
	printf("&array[1][0]:%p\n",&array[1][0]);//直接取array的第二行数组的首元素地址 
	printf("**(array+1):%d\n",**(array+1));//取第二行数组首元素的值(是该地址上对应的值) 
	printf("*(*(array+1)+3):%d\n",*(*(array+1)+3));//相当于在第二行首元素地址上进行指针运算,向后偏移3位,得到首元素后第三个元素的地址,再取值得到首元素后第三个元素的值 
	printf("array[1][3]:%d\n",array[1][3]);//*(array+1)+3 == &array[1][3]
 	//*(array + i) + j == &array[i][j]
 	//*(array + i) == array[i]
  	//*(*(*(array + i) + j) + k) == array[i][j][k]
	return 0;
}

初始化二维数组是可以偷懒的:

int array[][3] = {{0,1,2},{3,4,5}};//其中行数是可以省略的

关于数组指针与二维数组的关系:

int (*p)[3] = array;//array本身指的是第一行子数组本身

#include <stdio.h>


int main()
{
	int array[2][3] = {{0,1,2},{3,4,5}};
	int (*p)[3] = array;
	
	printf("**(p+1):%d\n",**(p+1));
	printf("**(array+1):%d\n",**(array+1));
	printf("array[1][0]:%d\n",array[1][0]);
	printf("*(*(p+1)+2):%d\n",*(*(p+1)+2));//p是指针变量,其值是数组的地址,p+1就是进行了指针运算,偏移到了后面1个数组上,就是第二个数组的地址
	//*(p+1)就是取了这个数组出来,类似于数组名,也就是数组的首元素地址,再对其求偏移,可得到后面第2个元素的地址*(p+1)+2,另外进行取值得到第二个数组的第三个元素 
	printf("*(*(array+1)+2):%d\n",*(*(array+1)+2));
	printf("array[1][2]:%d\n",array[1][2]);
 	
	
	return 0;
}

void指针

void指针称为通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给void指针。

#include <stdio.h>

int main()
{
	int num = 1024;
	int *pi = &num;
	char *ps = "Love";
	void *pv;
	
	pv = pi;
	printf("pi:%p,pv:%p\n",pi,pv);
	printf("*pv:%d\n",*(int *)pv);//(int *)是改变指针类型的操作,这里讲void指针强转为int指针,再加解引用,也就是取值符 
	//因为void指针不能解引用!!! 
	pv = ps;
	printf("ps:%p,pv:%p",ps,pv);
	printf("*pv:%s",(char *)pv);
	
	return 0;
}

NULL指针

#define NULL ((void *)0);//相当于宏定义,空指针,不指向任何数据,因为地址0通常不存储数据,所以把它定义成一个void指针

当不知道要将指针初始化为什么地址时,先初始化为NULL,这样就可以解引用之前先检查是否为NULL,为NULL则报错,不会像野指针一样,不报错难以排查

NUL与NULL不同,NULL用于指针和对象,表示控制,指向一个不被使用的地址;而NUL表示'\0'表示字符串的结尾。

#include <stdio.h>

int main()
{
	int *p1;
	int *p2 = NULL;
	
	printf("%d\n",*p1);//野指针会乱指一气,但随机地址如果没有存放别的值,就会随机输出,但不会报错,难以排查 
	printf("%d\n",*p2);//但NULL指针是非法的,不会无法排查 
	
	return 0;
	
}

指向指针的指针

#include <stdio.h>

int main()
{
	int num = 520;
	int *p = &num;//p保存的是num的地址 
	int **pp = &p;//pp保存的是p的地址 
	
	printf("num:%d\n",num);
	printf("*p:%d\n",*p);//对p做取值,即num的地址 
	printf("**p:%d\n",**pp);//对pp取值得到p的值num的地址,再进行一次取值,得到num的取值 
	printf("&p:%p,pp:%p\n",&p,pp);//&p对p进行取址,pp看pp这个指针里存放的哪个变量的地址,其实pp=&p 
	printf("&num:%p,p:%p,*pp:%p\n",&num,p,*pp);//&num对num进行取址,p看p这个指针里面存放的是哪个变量的地址,*pp对pp进行解引用,也就是求保存的地址对应的变量的值,即p的值 
	//实际上我发现,直接返回pp本身的值,也就是pp存放的p的地址,进行*pp,也就是对pp进行解引用,得到的是p的取值,也就是p存放的num的地址 
	
	return 0;
}

指针数组与指向指针的指针

#include <stdio.h>

int main()
{
	int array[][4] = {
		{0,1,2,3},
		{4,5,6,7},
		{8,9,10,11}};
		
	int (*p)[4] = array;//数组指针,是一个指向数组的指针,p就是指向二维数组array的指针,其偏移量就是二维数组的元素长度4
	int i,j; 
	
	for (i = 0;i < 3;i++)
	{
		for (j = 0;j < 4;j++)
		{
			printf("%2d",*(*(p + i) + j));
		}
		printf("\n");
	}

	return 0;
	
}

常量和指针

普通的:520,'a',3.14

宏定义的:#define PRICE 520;

const关键字修饰符:const int price = 520;

指向常量的指针:

指针可以修改为指向不同的常量

指针可以修改为指向不同的变量

可以通过解引用来读取指针指向的数据

不可以通过解引用修改指针指向的数据

#include <stdio.h>

int main()
{
	/* 
	const float pi = 3.14;//定义一个只可读的常量pi 
	printf("%f\n",pi);
	
	//pi = 3.1415;//修改会报错 
	*/
	
	int num = 520;
	const int cnum = 880;
	const int *pc = &cnum;
	
	printf("cnum:%d,&cnum:%p\n",cnum,&cnum);
	printf("*pc:%d,pc:%p\n",*pc,pc);
	
	pc = &num;//修改pc的指向,指向num的地址
	printf("num:%d,&num:%p\n",num,&num);
	printf("*pc:%d,pc:%p\n",*pc,pc);
	
	num = 1024;//通过修改num的值,来修改pc指向的地址的值 
	printf("num:%d,&num:%p\n",num,&num);//但是指针不能被修改,如*p = 1024;是不允许的 
	printf("*pc:%d,pc:%p\n",*pc,pc);
	
	
	return 0;
}

指向非常量的常量指针

指针自身不可被修改

指针指向的值可以被修改

指向常量的常量指针:

指针自身不可被修改

指针指向的值也不可被修改

#include <stdio.h>

int main()
{
	int num = 520;
	const int cnum = 880;
	int * const p = &num;
	
	*p = 1024;
	printf("*p:%d\n",*p);
	
	p = &cnum;//指向常量的指针是不可以被修改的,这里会报错
	printf("*p:%d\n",*p);
	
	return 0;
}

函数

函数定义:

类型名 函数名(参数列表)

{

    函数体

}

函数声明:

所谓声明,就是告诉编译器我要使用这个函数,现在没有找到它的定义不要紧,请不要报错,待会会补上

#include <stdio.h>

//编写一个函数sum,由用户输入参数n,计算1+2+..+n的结果并返回 
 
int sum(int n);//形参 

int sum(int n)
{
	int result = 0;
	do
	{
		result += n;
	} while(n-- > 0);
	
	return result;
}

int main()
{
	int n,result;
	
	printf("请输入n的值:");
	scanf("%d",&n);
	
	result = sum(n);//实参传入 
	
	printf("1+2+...+(n-1)+n的结果是:%d\n",sum(n));
	
	return 0;
}

参数与指针

传值与传址

版本1:传值

#include <stdio.h>
//数据的互换,直接进行数值的传入 
void swap(int x,int y);

void swap(int x,int y)
{
	int temp;
	
	printf("In swap,互换前:x = %d,y = %d\n",x,y);
	
	temp = x;
	x = y;
	y = temp;
	
	printf("In swap,互换后:x = %d,y = %d\n",x,y);
}

int main()
{
	int x = 3,y = 5;
	printf("In main,互换前:x = %d,y = %d\n",x,y);
	
	swap(x,y);
	
	printf("In main,互换后:x = %d,y = %d\n",x,y);//传值的时候,这里不会被函数的变化所改变 
	
	return 0;
}

版本2:传址

#include <stdio.h>
//数据的互换 
void swap(int *x,int *y);//声明x,y指针 

void swap(int *x,int *y)//对*x,*y两个指针传入地址 
{
	int temp;
	
	printf("In swap,互换前:x = %d,y = %d\n",*x,*y);//*x,*y即对x,y进行解引用,获取传入的&x,&y对应的值,注意这里的x,y的不同 
	
	temp = *x;//依然解引用获取x的取值,传给int型变量temp 
	*x = *y;//解引用:将y的取值传给x 
	*y = temp;//将temp变量的值传给y的解引用 
	
	printf("In swap,互换后:x = %d,y = %d\n",*x,*y);//解引用后打印x,y的数值 
}

int main()
{
	int x = 3,y = 5;
	printf("In main,互换前:x = %d,y = %d\n",x,y);
	swap(&x,&y);//传入x,y的地址 
	printf("In main,互换后:x = %d,y = %d\n",x,y);//传址的话,就可以改变这里的值了 
	return 0;
}

用数组传参数

#include <stdio.h>

void get_array(int a[10]);

void get_array(int a[10])
{
	int i;
	a[5] = 520;//会修改到main函数中,因为数组也有指针的性质
	
	for (i = 0;i < 10;i++)
	{
		printf("a[%d] = %d\n",i,a[i]);
	}
}

int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	int i;
	
	get_array(a);
	
	for (i = 0;i < 10;i++)
	{
		printf("a[%d] = %d\n",i,a[i]);
	}
 	
	return 0;
}

关于传入函数中的数组究竟是不是数组本身???

#include <stdio.h>

void get_array(int a[10]);

void get_array(int a[10])//这里就是一个地址 
{
	printf("sizeof b:%d\n",sizeof(a));//所以打印出来只占4个字节,就是int型地址的内存 
}

int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	
	printf("sizeof a:%d\n",sizeof(a));//这里打印出来的是数组的长度,也就是10个int型元素的内存,10*4 = 40个字节 
	get_array(a);
 	
	 
	return 0;
	
}

可变参数

#include

va_list//一个类型,va就是可变参数的意思

va_start//3个宏定义

va_arg

va_end

感觉可变参数的目的就是,使参数的个数可以随意调整

#include <stdio.h>
#include <stdarg.h>

int sum(int n,...);//...指的是参数个数不确定 

int sum(int n,...)
{
	int i,sum = 0;
	va_list vap;//用vap_list这个类型来定义参数列表,vap相当于一个字符指针 
	
	va_start(vap,n);//定义之后,把vap传给va_start这个宏,传入参数n,这里就相当于对字符指针进行计算而已 
	for (i = 0;i < n;i++)
	{
		sum += va_arg(vap,int);//用va_arg来获取后面的每一个参数的值,就是...代指的参数 ,并且说明参数类型为int 
	}
	va_end(vap);//用va_end来关闭参数列表 
	
	return sum;
}

int main()
{
	int result;
	
	result = sum(3,1,2,3);
	printf("result1 = %d\n",result);
	
	result = sum(5,1,2,3,4,5);
	printf("result2 = %d\n",result);
	
	result = sum(6,1,2,3,-1,99,1000);
	printf("result3 = %d\n",result);

	return 0;
 } 

指针函数与函数指针

指针函数

#include <stdio.h>

char *getWord(char c);

char *getWord(char c)//指针函数 
{
	switch(c)
	{
		case 'A':return "Apple";//实际上返回的是"Apple"中'A'的地址啦 
		case 'B':return "Banana";
		case 'C':return "Cat";
		case 'D':return "Dog";
		default:return "None";
	}
}

int main()
{
	char input;
	
	printf("请输入一个字母:");
	scanf("%c",&input);
	
	printf("%s\n",getWord(input));//这里getWord(input)应该给出一个字符串,通常是没有类型来定义字符串的,都是用char类型的指针来定义字符串
	//因为char类型的指针是指向一个字符,如果用它指向字符串的第一个字符,然后字符串的截止于'\0'这个空字符的,因而知道首字符也就知道了字符串本身了 
	return 0;
}

不要返回局部变量的指针

#include <stdio.h>

char *getWord(char c);

char *getWord(char c)
{
	char str1[] = "Apple";//函数内部定义的变量是局部变量,出了函数就什么不是了 
	char str2[] = "Banana";
	char str3[] = "Cat";
	char str4[] = "Dog";
	char str5[] = "None";
	
	switch(c)
	{
		case 'A':return str1;//注意如果是局部变量就不能返回出函数去,但是字符串可以,为何呢,因为字符串的存储区域是在一个永久的区域里的,不在这个函数中 
		case 'B':return str2;
		case 'C':return str3;
		case 'D':return str4;
		default:return str5;
	}
}

int main()
{
	char input;
	
	printf("请输入一个字母:");
	scanf("%c",&input);
	
	printf("%s\n",getWord(input));
	return 0;
}

函数指针:指向函数的指针

指针函数 -> int *p();

函数指针-> int (*p)();

#include <stdio.h>

int square(int);

int square(int num)//接收到&num 
{
	return num * num;
}

int main()
{
	int num;
	int (*fp)(int);//第一个int是返回值类型,(int)是参数类型,fp这个指针指向的是一个函数 
	
	printf("请输入一个整数:");
	scanf("%d",&num);//接收的是num的地址,是一个整型 
	
	fp = &square;//把这个函数名squre赋给指针fp,其实函数名就是指这个函数的地址啦,square = &square
	
	printf("%d * %d = %d\n",num,num,(*fp)(num));
	
	return 0;
}

一个例子:将函数指针作为参数传入

#include <stdio.h>

int add(int,int);

int sub(int,int);

int calc(int (*fp)(int,int),int,int);

int add(int num1,int num2)
{
	return num1 + num2;
}

int sub(int num1,int num2)
{
	return num1 - num2;
}

int calc(int (*fp)(int,int),int num1,int num2)//用用函数指针作为传入的参数!!! 
{
	return (*fp)(num1,num2);
}

int main()
{
	printf("3 + 5 = %d\n",calc(add,3,5));
	printf("3 - 5 = %d\n",calc(sub,3,5));
	
	return 0;
}

将函数指针作为返回值

#include <stdio.h>

int add(int,int);

int sub(int,int);

int calc(int (*fp)(int,int),int,int);

int (*select(char))(int,int);//(*select(char))是指一个叫select的函数,有一个参数,是char型,再仔细看
//实际上(*)(int,int)这里就相当于是一个函数指针 


int add(int num1,int num2)
{
	return num1 + num2;
}

int sub(int num1,int num2)
{
	return num1 - num2;
}

int calc(int (*fp)(int,int),int num1,int num2)
{
	return (*fp)(num1,num2);
}

int (*select(char op))(int,int)//那么这里返回了啥呢,我觉得就是返回了一个函数指针(*)(int,int)
{
	switch(op)
	{
		case '+':return add;
		case '-':return sub;
	}
}

int main()
{
	int num1,num2;
	char op;
	int (*fp)(int,int);
	
	printf("请输入一个式子(如1+3):");
	scanf("%d%c%d",&num1,&op,&num2);
	
	fp = select(op);
	printf("%d %c %d = %d\n",num1,op,num2,calc(fp,num1,num2));
	return 0;
}

局部变量与全局变量

不同函数的变量不能互相访问

#include <stdio.h>

int main()
{
	int i = 520;
	
	printf("before,i = %d\n",i);
	for (int i = 0;i < 10;i++)//作用范围只在for语句中,所以不会和外面的i冲突,并且也不能改变外面的i的值 
	{
		printf("%d\n",i);
	}
	
	printf("after,i = %d\n",i);
	
	return 0;
}

全局变量

在函数中定义的叫局部变量,在函数外定义的,叫外部变量或全局变量;

全局变量可以被本程序中其他函数所共用。

#include <stdio.h>

void a();
void b();
void c();

int count;//全局变量,它会自动初始化为0 

void a()
{
	count++; 
}

void b()
{
	count++;
}

void c()
{
   int count;//注意这里定义了同名的局部变量,所以下面的count++的行为被看成局部变量count进行的,不会被其他函数记录下来
	count++;
}

int main()
{
	a();//记录1次
	b();//记录1次
	c();//未记录
	b();//记录1次
	
	printf("小郭今天被抱了%d次\n",count);//在各个函数中全局变量的行为,均被记录下来
 
  return 0;
}

先使用全局变量,后面再定义

#include <stdio.h>

void func();

void func()
{
	extern int count;//extern int意思是先跟编译器说明,这是要先使用后面才定义的全局变量,不要报错 
	count++; 
}

int count;//全局变量,它会自动初始化为0 

int main()
{
	func();
	printf("%d\n",count);
	
	return 0;
}

尽量少定义全局变量,因为全局变量要保留到程序结束才释放,占用内存大;另外若与局部变量重名多,会降低可读性。

作用域与链接属性

作用域:

当变量被定义在程序的不同位置时,它的作用范围不一样,也就是作用域不一样;

C语言的作用域:

代码块作用域:{}内就是一个代码块,一般就是从定义变量开始到右边的花括号为止

文件作用域:任何在代码块外声明的标识符都具有文件作用域,作用范围是从声明位置开始到文件结尾处,函数名也具有文件作用域

原型作用域:原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不写参数的名字(但参数类型是必须要写上的),其实函数原型的参数名还可以随便写一个名字,不必与形式参数相匹配(当然这样做没有任何意义)。

函数作用域:函数语句只适用于goto语句的标签,作用域将goto语句的标签限制在同一个函数内部,以及防止出现重名标签。

定义和声明:

定义就是编译器为变量申请空间并填充值,可以理解成int i = 10,赋值了就是定义了;声明是编译器知道变量被定义在其他地方了,int i,这种没赋值的可以理解成声明,但是别的地方没有进行定义的话,也会直接赋值为0啦。

局部变量既是定义,又是声明;

定义只能一次,声明可以有多次。

链接属性:

external(外部的):多个文件中声明的同名标识符表示同一个实体,一般默认文件作用域的标识符(如全局变量、函数)都是external属性,就是允许标识符跨文件访问,无论在不同文件中声明多少次,表示的都是同一实体

internal(内部的):单个文件中声明的同名标识符表示同一个实体,用static获取这种链接属性

none(无):声明的同名标识符被当作独立不同的实体

另外,只有文件作用域的标识符才有external或internal标识符,其他作用域都是none属性。

static关键字:用来修改拥有文件作用域的标识符的链接属性,可以将其默认的external->internal

生存期和存储类型

生存期:

静态存储期:文件作业域的变量属于静态存储,会在程序关闭之前,一致占据存储空间

自动存储期:会在程序运行中变化其存储空间,或释放其存储空间

存储类型:存储变量值的内存类型

自动变量auto:代码块中声明的变量就是auto的存储类型,用关键字auto描述,形式参数,局部变量都是默认的auto存储类型,可以加也可以不加;

寄存器变量register:该变量有可能被存放于CPU的寄存器中,它与auto的一致点在于,都拥有代码块作用域,自动存储器和空链接属性,但注意寄存器变量不能被取址运算符&获取地址,因为CPU寄存器地址不允许获取;

静态局部变量static:可以用来修饰局部变量,使其获得静态存储期,本来很快被释放,现在会保存到程序结束才释放

#include <stdio.h>

void func();

void func()//按理说count会执行完一次本函数即释放 
{
	static int count = 0;//静态局部变量,初值默认为0,让count获得了静态存储期,不会被释放了,要等到程序结束才释放 
	//每次都会留下上次保留的count值,而不是重置为初值0 
	printf("%d\n",count);//若不加static,就全是0,若加了static,就是输出0,1,2,...,9 
	count++; 
}

int main()
{
	int i;
	for (i = 0;i < 10;i++)
	{
		func();

	}
	
	
	return 0;
}

extern:

typedef:

快速排序

递归的思想用来一次次地重复过程,直到排完

#include <stdio.h>

void quick_sort();

void quick_sort(int array[],int left,int right)
{
	int i = left,j = right;
	int temp;
	int pivot;
	
	pivot = array[(left + right) / 2];
	
	while (i <= j)
	{
		//从左到右找到大于等于基准点的元素
		while (array[i] < pivot)
		{
			i++;
		 } 
		 //从右到左找到小于等于基准点的元素
		 while (array[j] > pivot)
		 {
		 	j--;
		  } 
		  //如果i <= j,则互换
		  if (i <= j)
		  {
		  	temp = array[i];
		  	array[i] = array[j];
		  	array[j] = temp;
		  	i++;
		  	j--;
		   } 
	}
	if (left < j)//从右向左的j如果没有到达最左端,就递归下去,直到排到最左边 
	{
		quick_sort(array,left,j);
	}
	
	if (i < right)//这是一样的,如果从左向右的i没有到达最右端,也就一直递归下去,直到排到最右边 
	{
		quick_sort(array,i,right);
	}
	
}

int main()
{
	int array[] = {73,108,22,3,567,8907,4,23,678,890,1679,2356,76432,23456,654,3129,1008};
	
	int i,length;
	
	length = sizeof(array) / sizeof(array[0]);
	quick_sort(array,0,length - 1);
	
	printf("排序后的结果是;");
	for (i = 0;i < length;i++)
	{
		printf(" %d",array[i]);//按排序后的顺序打印数组元素 
	 } 
	 
	 putchar('\n');//打印换行符 
	 
	return 0;
}

动态内存管理

以下都是堆函数:

malloc:申请动态内存空间

void *malloc(size);//void类型指针可以修改成任何类型的数据,如果调用失败返回为NULL,另外如果字节数参数size为0,则返回值也为NULL,但不意味着调用失败;注意,void *可以省略不写

malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的指针。

#include <stdio.h>
#include <stdlib.h> 

int main()
{
	int *ptr;
	ptr =(int *)malloc(sizeof(int));//相当于为指针变量ptr申请了一个int整型的空间(4字节)
	//并将这块空间的地址传给指针ptr,又因为返回的是void *指针,所以转化为int *指针 
	if (ptr == NULL)//判断是否调用malloc成功,不是NULL就成功 
	{
		printf("内存分配失败");
		exit(1);//退出程序,它和malloc需要加载stdlib头文件 
	 } 
	 
	 printf("请输入一个整数:");
	 scanf("%d",ptr);//输入的值存放在为指针ptr申请的内存空间中 
	 
	 printf("你输入的整数是%d\n",*ptr); 
	
	free(ptr)//释放malloc申请的空间 
	return 0;
}

free:释放动态内存空间

void free(void *ptr);

free函数释放ptr指针参数指向的内存空间,该内存空间必须是malloc、calloc或realloc函数申请的,如果ptr指向NULL,则不执行任何操作,注意该函数不会改变ptr的值,也就是它仍指向原来的地方,只是该空间变成了非法空间,也就是ptr变成了类似于野指针。。。

!!!!!!关于内存泄露

因为C语言没有自己的GC垃圾回收机制,所以每次malloc一次空间,用完就要立刻free掉它,如果一直不释放空间并堆积下去,会耗尽内存,导致宕机!!!CPU会主动杀死该进程。

!!!!!!关于内存地址丢失

#include <stdio.h>
#include <stdlib.h> 

int main()
{
	int *ptr;
	int num = 123;
	
	ptr =(int *)malloc(sizeof(int));//相当于为指针变量ptr申请了一个int整型的空间(4字节)
	//并将这块空间的地址传给指针ptr,又因为返回的是void *指针,所以转化为int *指针 
	if (ptr == NULL)//判断是否调用malloc成功,不是NULL就成功 
	{
		printf("内存分配失败");
		exit(1);//退出程序,它和malloc需要加载stdlib头文件 
	 } 
	 
	 printf("请输入一个整数:");
	 scanf("%d",ptr);//输入的值存放在为指针ptr申请的内存空间中,注意scanf("数据类型",地址表列),也就是按照地址输入值的!!! 
	 //这里指针变量ptr存的就是指向的地址,这个操作相当于向这个地址里存值了 
	 printf("你输入的整数是%d\n",*ptr); //输出是按照具体的值打印的,解引用指针ptr,就得到ptr指向的内存中存的值
	
	ptr = &num;//让ptr指向num的地址处,malloc的内存就没有指针指向它了,它变成了垃圾内存。。。 
	printf("你输入的整数是%d\n",*ptr); //打印出来的是后面的num的值 
	
	free(ptr);//因为free只能释放malloc申请的内存空间,发现ptr指向的不是malloc申请的空间了,直接报错!!! 
	
	return 0;
}

malloc还可以申请一块任意尺寸的内存空间

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int *ptr = NULL;
	int num,i;
	
	printf("请输入待录入整数的个数:");
	scanf("%d",&num);
	
	ptr = (int *)malloc(num * sizeof(int));
	for (i = 0;i < num;i++)
	{
		printf("请录入第%d个整数:",i+1);
		scanf("%d",&ptr[i]);
	}
	printf("你录入的整数是:");
	for (i = 0;i < num;i++)
	{
		printf("%d ",ptr[i]);
	}
	putchar('\n');
	
	free(ptr);
	return 0;
}

初始化内存空间

以mem开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中;

memset:使用一个常量字节填充

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N 10

int main(void)
{
	int *ptr = NULL;
	int i;
	
	ptr = (int *)malloc(N * sizeof(int));
	if (ptr == NULL)
	{
		
		
		exit(1);
	}
	memset(ptr,0,N * sizeof(int));//初始化一下内存空间,在申请的每个单位空间中填充常量0 
	
	for (i = 0;i < N;i++)
	{
		printf("%d ",ptr[i]);
	}
	putchar('\n');
	
	
	free(ptr);
	return 0;
}

memcpy:拷贝内存空间

memmove:拷贝内存空间

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(void)
{
	int *ptr1 = NULL;
	int *ptr2 = NULL;
	//第一次申请的内存空间 
	ptr1 = (int *)malloc(10 * sizeof(int));
	
	//进行若干操作后发现ptr1申请的内存空间不够用了
	 
	//第二次申请的内存空间 
	ptr2 = (int *)malloc(20 * sizeof(int));
	
	//将ptr1的数据拷贝到ptr2中 
	memcpy(ptr2,ptr1,10);//memcpy(目标,源,数量);
	
	//释放ptr1 
	free(ptr1);
	
	//对ptr2申请的内存空间进行若干操作
	
	//释放ptr2 
	free(ptr2); 
	return 0;
}

calloc:申请并初始化一系列内存空间

int *ptr = (int *)calloc(8,sizeof(int));//直接一步申请空间并初始化

等价于

int *ptr = (int *)malloc(8 * sizeof(int));//先申请空间

memset(ptr,0,8 * sizeof(int));//再初始化

realloc:重新分配内存空间

void *realloc(void *ptr,size_t size);

一般要求新分配的内存空间更大,否则可能造成数据丢失。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(void)
{
	int i,num,count = 0;
	int *ptr = NULL; 
	
	do
	{
		printf("请输入一个整数(输入-1表示结束):");
		scanf("%d",&num);//给num传入一个值 
		count++;//count自增 
		
		ptr = (int *)realloc(ptr,count * sizeof(int));//给ptr传入count个int型数值,就给ptr分配count个int所需空间 
		if (ptr == NULL)
		{
			exit(1);
		}
		
		ptr[count - 1] = num;//将num的值传给ptr 
	} while(num != -1);
	
	printf("输入的整数分别是:");
	for (i = 0;i < count;i++)//按顺序打印出来 
	{
		printf("%d ",ptr[i]);
	}
	putchar('\n');
	
	//释放ptr
	free(ptr); 
	return 0;
}

宏定义

#define:宏定义相当于预编译,就是在编译之前就进行机械替换,然后宏定义不能加";"。

#include <stdio.h>

#define R 6371
#define PI 3.14
#define V PI * R * R * R * 4 / 3

int main(void)
{
	printf("地球的体积是:%.2f\n",V);
	
	return 0;
}
//含参数的宏定义
#include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))//为什么这里都要加()?
//因为如果输入x+1,y+1,不加括号是这样:x+1 > y+1 ? x+1 : y+1,想象一下,如果标识符优先级按顺序还好,如果结合性错误,就会出现意想不到的后果
//加了()就可以避免了



int main(void)
{
	int x,y;
	printf("请输入2个整数:");
	scanf("%d%d",&x,&y);
	printf("较大的数是%d\n",MAX(x,y));
	
	return 0;
}

#undef:终止宏定义作用域

结构体

struct 结构体名称

{

    结构体成员1;

    结构体成员2;

    结构体成员3;

    ....

};

定义结构体变量

struct 结构体名称 结构体变量名;

#include <stdio.h>

struct Book
{
	char title[128];
	char author[40];
	float price;
	unsigned int date;
	char pulisher[40];
};
//} book;//在这里定义是全局变量 

int main(void)
{
	struct Book book;
	
	printf("请输入书名:");
	scanf("%s",book.title);
	printf("请输入作者:");
	scanf("%s",book.author);
	printf("书名:%s\n",book.title);
	printf("作者:%s\n",book.author);
	
	return 0;
}

初始化结构体变量

struct Book book = {

    "大话数据结构",

    "程杰"

};

struct Book book = {

    .title = "大话数据结构",

    .author = "程杰"

};

#include <stdio.h>

struct Date//定义结构体变量
{
	int year;
	int month;
	int day;
};

struct Book
{
	char title[128];
	char author[40];
	float price;
	struct Date date;//定义Date结构体型变量 
	char publisher[40];
} book = {
		"<大话数据结构>",
		"程杰",
		 59.0,
		 {2017,11,11},//结构体就用{ , , }输入 
		 "清华大学出版社"
};//在这里初始化全局变量book 

int main(void)
{
	
	printf("书名:%s\n",book.title);
	printf("作者:%s\n",book.author);
	printf("价格:%.2f\n",book.price);
	printf("出版日期:%d-%d-%d\n",book.date.year,book.date.month,book.date.day);//层层索引,访问取值
	printf("出版社:%s\n",book.publisher);
	 
	return 0;
}

结构体数组

定义1:

struct 结构体名

{

    结构体成员;

} 数组名[长度];

定义2:

struct 结构体名

{

    结构体成员;

};

struct 结构体名 数组名[长度];

初始化结构体数组:

struct Book book[2] = {

    {"《大话数据结构》","程杰",59.0,{2017,11,11},"清华大学出版社"},

    {"《深度学习》","伊恩",49.5,{2016,11,11},"人民邮电出版社"}

};

结构体指针

struct Book *pt;

pt = &book;//与数组不一样,,需要取址运算

通过结构体指针访问结构体成员:

(*结构体指针).成员名  //*解引用变成结构体,然后.运算,访问成员

结构体指针->成员名 //效果一样,但是方便

#include <stdio.h>

struct Date
{
	int year;
	int month;
	int day;
};

struct Book
{
	char title[128];
	char author[40];
	float price;
	struct Date date;//定义Date结构体型变量 
	char publisher[40];
} book = {
		"<大话数据结构>",
		"程杰",
		 59.0,
		 {2017,11,11},//结构体就用{ , , }输入 
		 "清华大学出版社"
};//在这里初始化全局变量book 

int main(void)
{
	struct Book*pt;//定义结构体指针
	pt = &book;//结构体指针要传入结构体的地址
	
	
	printf("书名:%s\n",(*pt).title);
	printf("作者:%s\n",(*pt).author);
	printf("价格:%.2f\n",(*pt).price);
	printf("出版日期:%d-%d-%d\n",(*pt).date.year,(*pt).date.month,(*pt).date.day);//结构体指针访问结构体的成员变量1
	printf("出版社:%s\n",(*pt).publisher);
	 
	printf("********************分隔符***********************\n");
	
	printf("书名:%s\n",pt->title);
	printf("作者:%s\n",pt->author);
	printf("价格:%.2f\n",pt->price);
	printf("出版日期:%d-%d-%d\n",pt->date.year,pt->date.month,pt->date.day);//结构体指针访问结构体的成员变量2
	printf("出版社:%s\n",pt->publisher);
	 
	return 0;
}

结构体赋值

#include <stdio.h>

int main(void)
{
	struct Test
	{
		int x;
		int y;
	} t1,t2;
	
	t1.x = 3;//结构体成员变量可以直接赋值 
	t1.y = 4;
	
	t2 = t1;//同类型的结构体之间也可以直接赋值 
	
	printf("t2.x = %d,t2.y = %d\n\n",t2.x,t2.y);
}

传递结构体变量

#include <stdio.h>
//声明一个结构体Date 
struct Date
{
	int year;
	int month;
	int day;
};
//声明一个结构体Book 
struct Book
{
	char title[128];
	char author[40];
	float price;
	struct Date date;//结构体嵌套
	char publisher[40]; 
};
//定义一个函数getInput 
struct Book getInput(struct Book book)//这里的返回值类型是struct Book,函数名getInput,形参为Book型结构体,命名为book 
{
	printf("请输入书名:");
	scanf("%s",book.title);//这里可以直接对传入的形参的变量进行修改 
	printf("请输入作者:");
	scanf("%s",book.author);
	printf("请输入价格:");
	scanf("%f",&book.price);
	printf("请输入日期:");
	scanf("%d-%d-%d",&book.date.year,&book.date.month,&book.date.day);
	printf("请输入出版社:");
	scanf("%s",book.publisher);
	
	return book;//这个值返回给b 
}
//定义一个函数printBook 
void printBook(struct Book book)
{
	printf("书名:%s\n",book.title);
	printf("作者:%s\n",book.author);
	printf("价格:%.2f\n",book.price);
	printf("日期:%d-%d-%d\n",book.date.year,book.date.month,book.date.day);
	printf("出版社:%s\n",book.publisher);
}
//main函数 
int main(void)//这里也可以看出传入参数列表是void,返回值类型为int,函数名是main 
{
	struct Book b;//定义一个Book型结构体变量b
	
	printf("输入一本书的信息...\n");
	b = getInput(b);//这是结构体实参,传递给函数getInput的结构体形参,在这里Book型结构体变量接收到了getInput返回出来的结构体book 
	putchar('\n'); //打印换行 
	
	printf("录入完毕,现在开始打印验证...\n");
	printf("打印一本书的信息...\n");
	
	printBook(b);//相当于又是调用函数printBook,一样的传参,但是这次不需要返回值
	
	return 0;
}

关于结构体指针怎么用

#include <stdio.h>
//声明一个结构体Date 
struct Date
{
	int year;
	int month;
	int day;
};
//声明一个结构体Book 
struct Book
{
	char title[128];
	char author[40];
	float price;
	struct Date date;//结构体嵌套
	char publisher[40]; 
};
//定义结构体指针 
void getInput(struct Book *book);
void printBook(struct Book *book); 

//定义一个函数getInput 
void getInput(struct Book *book)//指针接收地址 
{
	printf("请输入书名:");
	scanf("%s",book->title);//对指针指向的结构体的成员变量进行输入赋值 
	printf("请输入作者:");
	scanf("%s",book->author);
	printf("请输入价格:");
	scanf("%f",&book->price);//
	printf("请输入日期:");
	scanf("%d-%d-%d",&book->date.year,&book->date.month,&book->date.day);
	printf("请输入出版社:");
	scanf("%s",book->publisher);
}
//定义一个函数printBook  
void printBook(struct Book *book)//关于结构体指针 
{
	printf("书名:%s\n",book->title);//给对应的变量输入赋值 
	printf("作者:%s\n",book->author);
	printf("价格:%.2f\n",book->price);
	printf("日期:%d-%d-%d\n",book->date.year,book->date.month,book->date.day);
	printf("出版社:%s\n",book->publisher);
}
//main函数 
int main(void)
{
	struct Book b;

	printf("输入一本书的信息...\n");
	getInput(&b);//为指针传入结构体地址 
	putchar('\n'); 
	
	printf("录入完毕,现在开始打印验证...\n");
	printf("打印一本书的信息...\n");
	
	printBook(&b);
	
	return 0;
}

单链表

一个节点[信息域(存放节点的内容)+指针域(用来指向下一个节点)];

先是头指针指向第一个节点,节点类似于数组里的元素,包括信息域和指针域,信息域用来存放内容,指针域则是用来指向下一个节点的,直到最后一个节点的指针域指向NULL结束。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//定义一个单链表 
struct Book
{
	char title[128];//信息域 
	char author[40];
	struct Book *next;//指针域,指向一个和它一模一样的节点 
};

void getInput(struct Book *book)//接收到实参,开始对各变量进行传值 
{
	printf("请输入书名:");
	scanf("%s",book->title);//传入值 
	printf("请输入作者:");
	scanf("%s",book->author);
}

//头插法在链表中插入一个节点
void addBook(struct Book **library)//这是一个指向头指针的指针,这里想要传入的是头指针library的地址 、
//指针要修改谁的值,就传入谁的地址
//int *pb = &book;//这其实就是传入book的地址,那如果可以改变book的地址,自然可以改变对应的book的内容了,如果book是一个指针,就是上面的情形了 
{
	struct Book *book,*temp;//定义出来结构体指针,*book是指新创的节点指针,而*temp是用来中转的临时节点 
	
	book = (struct Book *)malloc(sizeof(struct Book));//为book申请了内存空间,其实就是一个地址 
	if (book == NULL)
	{
		printf("内存分配失败");
		exit(1);
	}
	
	getInput(book);//把指针变量中保存的地址“它址”作为实参传给getInput的指针形参,book指向的内存地址中有值了! 
	
	if (*library != NULL)//如果头指针不指向NULL,说明链表中已经有节点了,这时候指向新节点,要用*temp来临时保存老节点,防止丢失它的地址 
	{
		temp = *library;//library保存的是老节点指针(结构体指针)地址,解引用出来就是老节点指针,传给临时指针变量temp(也是结构体指针) 
		*library = book;
		book->next = temp;
	}
	else//若头指针指向的是NULL,说明链表为空 
	{
		*library = book;//此时就让头指针直接指向book节点 
		book->next = NULL;//book的next指向NULL,也就是指针域指向NULL
	}
}
void printLibrary(struct Book *library)
{
	struct Book *book;
	int count = 1;
	
	book = library;
	while (book != NULL)
	{
		printf("Book%d:",count);
		printf("书名:%s",book->title);
		printf("作者:%s",book->author);
		book = book->next;
		count++;
	}
}
void releaseLibrary(struct Book *library)
{
	while (library != NULL)
	{
		free(library);
		library = library->next;
	}
}

int main(void)
{
	struct Book *library = NULL;//头指针(指向指针的指针),指向的是NULL,表示空的单链表,可以理解成library指针变量中了保存下一个节点(节点就是指针)的地址,对library一层解引用可以得到下一节点的值 
	int ch;
	
	while (1)
	{
		printf("请问是否需要录入书籍信息(Y/N):");
		do 
		{
			ch = getchar();
		} while (ch != 'Y' && ch != 'N');
		if (ch == 'Y')
		{
			addBook(&library);//要改动头指针的值(头指针的指向),要给指向头指针的指针传入头指针的地址进行修改 
		}
		else
		{
			break;
		}
	}
	printf("请问是否需要打印图书信息(Y/N):");
	do 
	{
		ch = getchar();
	} while (ch != 'Y' && ch != 'N');
	if (ch == 'Y')
	{
		printLibrary(library);
	}
	
	releaseLibrary(library);
	
	return 0;
}

typedef

给数据类型起别名:typedef int integer; typedef int *ptrint;//像这样可以替换基本数据类型和指针

它与#define的机械替换不同,它就是直接对数据类型进行替换,并且这种替换是封装过的

还可以给结构体起别名:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Date
{
	int year;
	int month;
	int day;
} DATE;//给结构体起别名 

int main(void)
{
	DATE *date;//可以用别名啦 
	date = (DATE *)malloc(sizeof(DATE));
	if (date == NULL)
	{
		printf("内存分配失败\n");
		exit(1);
	}
	
	date->year = 2022;
	date->month = 3;
	date->day = 16;
	printf("今天的日期是:%d-%d-%d",date->year,date->month,date->day);
	
	return 0;
}

共用体

union 共用体名称

{

        共用体成员1;

        共用体成员2;

        共用体成员3;

        ......

};

共用体是多个成员使用同一个地址,同一时间只能使用一个成员;共用体所需空间至少要可以存储最大成员

#include <stdio.h>
#include <string.h>


union Test//共用体可以没有名称
{
	int i;
	double pi;
	char str[6];
};//声明共用体

int main(void)
{
	union Test test;//定义共用体变量
	//union Test test = {520};//初始化第一个值
 	//union Test test = {.i  = 520};//初始化指定成员
  	//union Test test = test1;//共用体传递初始化
	test.i = 520;
	test.pi = 3.14;
	strcpy(test.str,"FishC");
	
	printf("add of test.i:%p\n",&test.i);//事实上,他们的地址一样 
	printf("add of test.pi:%p\n",&test.pi);
	printf("add of test.str:%p\n",&test.str);
	
	
	printf("add of test.i:%d\n",test.i);//打印值会互相覆盖 
	printf("add of test.pi:%.2f\n",test.pi);
	printf("add of test.str:%s\n",test.str);//只有最后这个值可以正确打印 
	
	return 0;
}

枚举类型

如果变量只有几种可能的值,就可以将其定义为枚举类型。

声明枚举类型:

enum 枚举类型名称{枚举值名称,枚举值名称,...};

定义枚举变量:

enum 枚举类型名称 枚举变量1,枚举变量2,...;

#include <stdio.h>
#include <time.h>

int main(void)
{
	enum Week {sun,mon,tue,wed,thu,fri,sat};//这里的mon,tue等叫枚举常量,不赋值就默认从0开始初始化 
	enum Week today;//定义这个枚举变量,它可以有以上几种取值 
	
	struct tm *p;
	time_t t;
	
	time(&t);
	p = localtime(&t);
	
	today = p->tm_wday;
	
	switch(today)
	{
		case mon:
		case tue:
		case wed:
		case thu:
		case fri:
			printf("工作\n");
		case sat:
		case sun:
			printf("放假\n");
		default:
			printf("error\n"); 
	}
 } 

C++特性

命名空间的使用(嵌套,合并,以及命名污染)

namespace xjt
{
    int printf = 1;
    int rand = 2;
    int Add(int a,int b)
    {
        return a + b;
    }
    namespace xjt2 //命名空间namespace的嵌套
    {
        int a = 0;
        int Sub(int a,int b)
        {
            return a - b;
        }
    }
}

namespace xjt //相同名称的namespace会默认合并到一起
{
    int a = 3;
    int b = 1;
}


#include <iostream>

int main()
{
    printf("%p\n",xjt::rand); //注意printf是默认取的命名空间中的printf的地址的
}
//另外需要注意,直接采用using namespace xjt;会默认所有变量首选为命名空间中的变量,如果命名空间中有的变量是与关键字重合的,会造成污染。
//那我们就只需要完成using namespace xjt::rand;指定好要用的命名就行了,不要盲目全部导入

函数小tips

#include <iostream>
using namespace std;

void func(int a,int b = 1); //声明函数时,添加了默认值

void func(int a,int b) //定义函数时就不要再修改默认值了,不然g++编译器会编译错误
{
	cout << a << endl;
	cout << b << endl;
}

int main()
{
    func(1,1);
    func(1);//效果与func(1,1)是一样的,因为缺省了b = 1
}

函数重载

#include <iostream>
using namespace std;

//对形参个数或数据类型不同的函数来说,可以根据传入的实参来实现重载函数的调用
void func(double a)
{
    cout << a << endl;
}
//但注意,只有返回类型不同的函数并不能实现函数重载
void func(int a,int b)
{
	cout << a << endl;
	cout << b << endl;
	// cout << c << endl;
}

int main()
{
    func(1.1);
    func(1,1);
}

引用&

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int &b = a; //引用a,也就是给a取个别名b,但是b没有自己开辟出内存空间,它用的还是a的内存空间,由此可知,改变b的话,a也会跟着改变
    //但是注意,在定义的时候&才是取别名,在其他地方&b指的是取出b的地址
    cout << a << endl;
    cout << b << endl;

    b = 2; //可以改变一下b的值实验一下看看
    cout << a << endl;
    cout << b << endl;
}

指针(解引用)*

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int *c = &a;//&引用其实就是取址符,就是给定变量,取其地址,这里相当于把&a也就是a的地址,赋给指针变量c,c存储的就是a变量的地址了

    cout << c << endl;//这里其实会输出指针变量c的值,也就是a变量的内存地址
    cout << &a << endl;//可以和上面对比一下,是一样的值
    cout << (int *)&a << endl;//&a是a的地址,将其转换为指针变量的类型,也就是变成一个地址的格式,(int *)不在乎其后面的数值是什么,作用只是获取与该数值相等的地址而已,如果已经是地址了,就不必再加(int *)了

    cout << *c << endl;//*在这里其实就是取值符,什么取值符,就是给定地址,取出该地址上的变量值,*又叫解引用
    cout << a << endl;//c作为储存a的地址的指针变量,对其进行解引用得到的结果就是a的值
}

基础知识:

变量就是开辟的空间的内容,对变量进行处理,就是对所开辟的内存空间的内容进行处理;

64位平台:

整型:short 2bytes,int 4bytes,long 8bytes

字符型:char 1bytes(计算机只认识它的ASCII码值,不管是啥,都看成ASCII码)

实型(浮点数):float 4bytes,double 8bytes,指数类型123e3,指的是123*10^3

有符号数和无符号数:

有符号数(默认有符号数),最高位为1为负数,最高位为0为正数,这个位不算数据,后面的7位用来计算数据;

如1111 1111,为-127,0111 1111,为127,又因为1000 0000和0000 0000都分别为-0和+0,-0没有意义,所以直接用1000 0000来代表-128,所以八位二进制的取值范围-128~127;

无符号数(unsigned),所有位都是数据位,0000 0000~1111 1111,代表的范围就是0~255。

十六进制-->二进制:

0x1d3c,十六进制的一位换算成二进制四位,c=12=8+4+0+0=1100,3=0+0+2+1=0011,d=13=8+4+0+1=1101,1=0+0+0+1=0001,故而

0x1d3c = 0001 1101 0011 1100。

无符号数:原码==反码==补码;

有符号数的正数:原码==反码==补码

有符号数的负数:反码 == 原码符号位不变,其他按位取反,补码==反码+1

补码的意义:统一了0的编码。+-0的补码一致,均为0000 0000,解决了正负数原码相加出错的问题,补码就不会出错

#include <iostream>
#include <bitset>
using namespace std;

int main(int argc,char *argv[])
{
    // cout默认以十进制输出
    cout<<0b00001010<<endl;
    cout<<0123<<endl;
    cout<<0xab<<endl;

    // cout需要用bitset<位数>(数值),一般输出位数包括8,16,32
    cout<<bitset<16>(-10)<<endl; //以16位二进制形式输出-10,即1111111111110110
    //cout需要用oct来输出八进制
    cout<<oct<<0123<<endl;
    //cout需要用hex输出十六进制
    cout<<hex<<0xab<<endl;
    return 0;
}

'\0'空字符,'\b'删除

"abc"中有四个字符,分别是'a','b','c','\0',出现'\0'就代表字符串结束。

关键字:

const int data;  //const修饰data为只读变量,必须被初始化,且不能被赋值

存储在"符号常量表"中,不会立即给data开辟空间,对data取地址时,才会为他开辟空间,

#include <iostream>
#include <string>
using namespace std;

int main(){

    int a = 10;
    const int data = a; //当用变量对const变量初始化时,data就不会进入符号常量表,用常量对const变量初始化,data就会进入符号常量表暂存
    int *p = (int *)&data;
    *p = 2000;//a变量肯定就是指向a变量的地址对应的内存,p指针变量保存的是a变量的地址,*p变量就是指向p变量中存储的地址对应的内存,所以*p与a等价
    cout << "*p=" << *p << endl;
    cout << "data=" << data << endl;//若符号常量表中有值,就输出符号常量表中的值,没有进入符号常量表,就输出地址中的值
 
};

寄存器:

register int data = 0;//data暂存进入cpu中的寄存器内

不能对寄存器变量取地址,因为它不在内存中。。。

对于使用频率高的变量,我们采用寄存器变量,这样就不用总是与内存交互了,可以速度快点。

但是,也不一定就会放进寄存器,这其实是编译器决定的,修饰符只能作为参考,对于频繁使用的变量,即便没有register修饰,也会被编译器放入寄存器中。

强制访问内存:

volatile int data = 0;

假如某个变量频繁使用,那么就会被编译器放入寄存器中,然后很长时间才会与内存交互一次,这对于内存中变量会快速更新的情况来说,寄存器中的数据可能无法及时被同步过来,导致数据出现误差。

这目的就是防止编译器的自动优化。。。

sizeof关键字:

用来测量数据类型的内存长度。

typedef给已有的类型取个别名:

先声明,再使用,最后定义,只能用于全局变量

#include <iostream>
using namespace std;

//提前变量声明
extern float l_var;

int main(){

    //先使用
    cout<<"l_var"<<l_var<<endl;
 
};

//后定义
float l_var = 0;

对于多文件编译来说,需要用extern来声明另一个文件中定义的变量或函数,这样才能在本文件中调用另一个文件中的变量或函数~

结构体作为参数,再在函数内对该参数进行去sizeof()容易出问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会抓鼠鼠的阿猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值