立即学习:https://edu.csdn.net/course/play/8088/166680?utm_source=blogtoedu
// project1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <stdio.h>
int main()
{
//第二章 数据类型,运算符,表达式的介绍
printf("haohaohao\n");
unsigned int a = 49U;
long int b = 8787L;
float j = 23.9F;
1.68E2;//1.68*10 ^2 = 168;
float af1,af2;
//单精度,提供7位有效数字,内存4个字节,考虑四舍五入,保守估计6位有效数字
double ad1;//双精度,实型的在电脑当中是按照上面指数形式的储存的,8个字节,提供15~16位有效数字, 四舍五入,保守估计15位有效数字
"as";
'dd';
char c1, c2, c3;
c1 = 'a';//单引号,并不是将这个字符放到内存中,实际上是将这个字符对应的ASCII值放到内存中
float d = 0.51;//在之前的时候,显示的是0.509999999...,会出现精度损失,原因:先将10进制数转换为2进制,再转换为10精致显示,就会出现
//精度损失
//常规字符
'a';//这个是单个字符,所以是单引号
//字符串常量是双引号括起来的一堆字符
//double = 整形+ double;等等类型, 数值型数据之间的混合运算;
// 5/3 结果是1;7%4结果是3; 2+3*4结果是17;
//(--j)- (--5)-(-j); int azz = 4;
// azz++,是先用后加,先用azz的值,然后azz本身再加1,表达式值是5,而后再加1;
// ++azz;先加azz的值,然后使用azz的 ,表达式的值是6,执行完了也是6;
// char a = 90; int 占四个字节,char占1个字节,-128~127,
// a = 900;//此时会出现未预料的值。要赋合适的值。
//赋值原则:类型相同时最好的,如果类型不同,建议使用强制类型转换。当强制类型转换时,开发者必须自己明确知道不会溢出
short sa;//2个字节
int b = 30000;
sa = (short)b;//强制类型转换的目的是编译器不出现警告;
a += a -= a * a;
a += (a -= (a * a));
//逗号运算符格式: 表达式1,表达式2
//计算过程:先求解表达式1的值,在求解表达式2的值,整个表达式的值就是表达式2的值;
int j;
a = (4, 5);//这个a的值就是5了。
a = (3 + 4, 5 + 8);//答案是a=13;逗号运算符的优先级是最低的。
a = 3 * 5, a * 4; //解析:下一步的话就是a = 15,a *4; 再下一步就得出,整个表达式的值就是60,但a的值是15(因为逗号的优先级最低)
//逗号表达式有扩展形式
//表达式1,表达式2,表达式3,表示4.....
int x;
int a;
//a = 3;
//x = (a = 3, 6 * 3);//==>x = (a = 3, 18) ==> x = 18;
x = a = 3, 6 * a;// 最终 x = 3; a = 8;
printf("%d,%d,%d\n", 3, 4, 5);
return 0;
}
// Project1_第三章.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "stdio.h" //预编译命令,将stdio.h的文件都包含在此文件当中,
//""先在本文件所在的目录中寻找头文件,找不到再到 系统目录中寻找文件。包括自己写的
//<> 是先到系统目录先找头文件,不会到本文件所在目录寻找
int main()
{
//第三章,程序的基本结构和语句
//1,语句分类
//末尾加分号构成了C预言的一条语句;
//(1)只有九种控制语句 (1)if()~else~,(2)for(),(3)while()~ ,(4) do~while()
// (5)continue (6)break (7)switch (8)goto (9)return
//(2)函数调用语句 sizeof()
(3)表达式语句 3+5;
//int a;
//a = 3;//赋值语句(表达式语句)
(4)空语句: ; (啥也没干,没什么意义)
(5)复合语句,用{}大括号括起来的语句
// int k = 1;
// if (k > 1)
// {
// int aa;
// aa = 1;
// aa = 3;
// }
// else
// {
// ;
// ;
// }
// //c语句允许把一个语句拆开成几行写
// printf("jnknkjknknknkkjnkjnkjn \
// mkmkmlmlmm'm'lm'm"); //要顶格写,不然空格的内容也会被打印出来
//
// //2.程序的三种基本结构
// ///1>.顺序
// printf("11\n");
// printf("22\n");
// //2>.选择
// if ( 3 > 1)
// {
// printf("1\n");
// }
// else
// {
// printf("2\n");
// }
// //3>.循环
// //(3.1)当型循环结构
// int count = 5;
// while (count >= 0)
// {
// printf("count : %d \n", count);
// count--;
// }
// //(3.2)直到型循环结构 do .(A)..while ,(A条件至少被执行了一次,则继续执行)
// do
// {
// count++;
// } while (count >= 0);
// //《3.2下的派生结构》 :多分支选择结构
// int k = 5;
// switch (k)
// {
// case 1:
// {
// printf("1\n");
// }
// break;
// case 2:
// {
// printf("2\n");
// }
// break;
// case 3:
// {
// printf("3\n");
// }
// break;
// }
// //赋值语句的特殊结构
// int a = 5;//这个是定义的时候赋初值
//
// int a;
// a = 5;//这个才是赋值;
//数据输入和输出
//putchar(c) 向屏幕输出一个字符,只能输出一个字符,用处不太大,
//char a, b, c;
//a = 'F'; b = 'A'; c = 'S';
//putchar(a);
//putchar(b);
//putchar(c);
//a = 97; //ASCII
//b = 98; // ASCII
//putchar(a);// 'a'
//printf(格式控制,输出表列) %d,%o,%x ==>十进制,八进制,十六进制 %u,以十进制形式输出的unsigned型数字
/*
%c 输出一个字符
%s 输出一个字符串
%f 输出实数 float double
*/
//字符串和字符的区别是:字符串末尾有一个"\0",是字符串结束标志,是系统自动给我们加上去的,并不显示出来。
//float abc;
//printf("%.4f", abc);//显示小数点后四位数
//printf("5%c\n",'%');//这个可以显示%字符
//printf("5%s\n", '%');
数据输入,不常用
getchar();//从键盘输入一个字符,按回车后程序就会继续执行下去
//char c;
//c = getchar();
//putchar(c);
另一种写法
//printf("%c", getchar());//用户输入一个字符,并且摁回车之后,getchar()才会使程序继续执行下去
//int d;
scanf(),是格式化输入函数,用来输入任何类型的多个数据
scanf(格式控制,地址表列),摁回车之后输入结束
//scanf("%d %d %d", &a, &d, &c);//&符号代表的是地址运算符,表示该变量在内存中的地址,在键盘进行输入的时候,可以用空格,tab,回车,但不能用逗号,此外,要根据格式进行 严格进行输入;
//scanf("%d:%d:%d",&a,&d,&c);
//第四章 逻辑运算和判断选择
// 1,关系运算符,关系表达式,逻辑运算符,逻辑表达式
// 赋值运算符优先级 < 关系运算符 < 算术运算符
// == != 这两个关系运算符的优先级 < ,<= , > , >=
// c > a+b =====> c > (a+b)
//关系表达式:用关系运算符把两个表达式连接起来的式子;
//关系表达式的值是一个逻辑值,就是“真”或者“假”
//c语言中,真 用 True,也可用1表示,假用false表示,也可用0表示
// && 逻辑与,双目运算符 || 逻辑或,!逻辑非 单目运算符
//if (3 > 5 && (a = 8))
//{
// //.....
//}
//if语句
//三种形式1
//int x;
//x = 5;
//if (x > 5)
//{
// printf("true\n");
//}
///* if(x >5)
// printf("true\n");*/
2
//int w = 7, y = 8;
//if (w > 8)
//{
// printf("wrong\n");
//}
//else
//{
// printf("true\n");
//}
/*3.
if(表达式1) 语句1
else if (表达式2) 语句2
else if (表达式3) 语句3
......
else 语句n
*/
//条件运算符和switch语句
/*int a1 = 4, b1 = 5, max;
if(a1 > b1)
{
max = a1;
}
else
{
max = b1;
}*/
/*
switch(表达式)
case 常量表达式1:
一行或者多行要执行的语句
break;
case 常量表达式2:
一行或者多行要执行的语句
break;
case 常量表达式3:
//也可这里加一行:case 常量表达式4:
一行或者多行要执行的语句
break;
default:
一行或者多行要执行的语句
break;
*/
// max = (a1 > b1)? a:b; c语言中唯一一个三目运算符(条件运算符,优先级是13),那么托关系运算符的额条件高啊,所以表达式还可以写成
// max = a1 > b1 ? a:b; 另外,条件运算符本身的计算顺序是从右向左的。
//第五章-循环控制
//概述:(1)goto语句,无条件转向语句,用来跳转到某个程序位置来执行, 一般形式:goto语句标号,,其中语句标号是一个标识符。
// goto label1; goto 123;
// a) 与if语句一起构成循环结构
// b) 从循环体内跳到循环体外,一般不用,破坏了结构,一般用break
/*int ii = 1, sum = 0;
loop:
if (ii <= 100)
{
sum = sum + 1;
ii++;
goto loop;
}*/
/*int i = 1, sum = 0;
while (i <= 100)
{
sum += i;
i++;
}*/
/*int i = 1, sum = 0;
do
{
sum += i;
i++;
} while (i <= 100);*/
//for 可以取代while,但是是否取代,取决于你的编程习惯,针对于确定循环次数和不确定循环次数这样的情况,都可以处理
//特殊用法说明,(1)表达式1可以省略,但后边的不能省略,,当表达式1省略时,应该在for语句之前就给循环变量复初值
//(2表达式2也可以省略,,也就是不判断循环条件,那么循环就会无终止的运行下去,此时就要用break语句终止for循环)
//(3)表达式3也可以省略,但是我们要想办法保证循环能正常结束,,否则循环会无终止的进行下去,(就是要将表达式3的放在里面 ^_^)
//(4)可以省略表达式1,3,只给表达式2,但要想办法,哈哈,好说
//(5)三个表达式都省略,不设置初值,不判断条件,(认为条件一直为真),循环变量也增加,就导致无终止执行循环体,唉,这就得在其他地方加(1,2,3)条件了,哈哈哈
//(6)表达式1可以设置循环变量初值,也可以是与循环变量无关的其他表达式,
//int i, sum;
//i = 1;
//for (sum = 0; i <= 100; i++)
//{
// sum += i;
//}
(7)表达式1,3可以是简单表达式,也可以是逗号表达式,
//int i, j = 10000, sum;
//for (sum = 0, i = 1; i <= 100; i++, j--)
//{
// sum += i;
//}
//(8)表达式2的值一般是关系表达式,或者逻辑表达式&& ||,但是只要是其值非0,,就执行循环体,
//循环的嵌套,比较,break语句
/*int i, j;
for (i = 1; i <= 9; i++)
{
for (j = 1; j <= i; j++)
printf(" %d * %d = %d ", i, j, i * j);
printf("\n");
}*/
//几种循环语句的比较
//goto ,while,do while,for,对后面三种可以用break语句跳出循环,用continue语句结束本次循环,(break是结束当前的循环,continue不用在switch中)
//在整个c/c++中,break语句不能用于循环语句和switch语句之外的任何其他语句中,并且break语句出现在switch中,是用于跳出switch中。
printf("断点停止在这里\n");
return 0;
}
// Project1_continue.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
//extern int c1, c2;
//void lookValue()
//{
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2;
void funcTest();
int main()
{
//第六章 数组
//一维数组 定义:
//类型说明: 数组名[常量表达式];
//int a[10];//1.数组名就是变量名,2.数组名后面是方括号括起来的 int a[2 * 5];也可这样,代表以为数组a中有10个元素(相当于你定义了10个变量,每个变量都是整型)
//代表了a[0],----a[9]; 但要是a[10]= 1000;定义了的话,轻则导致程序运行出错,重则可能程序会不定时崩溃,(用了a[10]的话,这里会有致命错误)
//只定义,没有初始化,所以里面是不确定的事
//int a[10] = { 1,3,3,3,3,4 };//只给一部分数组元素初值,其他的数组元素系统会自动给0;
//int b[] = {1,3,4,5};
//二维数组[][],一般形式,类型说明符 数组名[常量表达式][常量表达式]
//float a[3][4];//a[0][0],a[0][1],a[0][2],a[0][3]
//a[1][0],a[1][1],a[1][2],a[1][3]
//a[2][0],a[2][1],a[2][2],a[2][3]
//float b[2][3][4];//三维数组(多维数组),在内存中排列数据:第一维下表变化最慢,最右边变化最慢,
//二维数组的引用
//数组名[下标][下标]
//二维数组初始化,1.分行给二维数组赋初值
//int a[3][4] = { {1,2,3,4},{5,6,7,7},{5,6,6,6} };//不常用
//int a[3][4] = { 1,2,3,4,5,6,7,7,5,6,6,6 };//对全部赋初值
//int a[3][4] = { {1},{5,67},{5,6} };//对部分赋初值
//int e[][4] = { {0,0,3},{},{0,10} };
//字符数组:用来存放字符数据的数组。字符数组中的一个元素存放一个字符。
//char c[10] = { 't','i','am','a'};//c[0]--c[9],1.逐个字符赋给数组中的元素。2.如果提供的初值个数和预定的数组长度相同,定义时间可以省略数组长度,系统
//char c[] = { 't','i','am','a' }; //会自动根据初值个数确定确定数组长度。3.初值个数 > 数组长度,则提示你语法错误。
//4.初值个数 < 数组长度,则只将这些字符赋值给数组前面的那些元素,其余的元素值可能会给\0,也可能无法无法确定,所以老师不建议使用这些无法确定的值
// 0 == '\0'
//char d[] = {" I am happy"};//系统会自动在字符串末尾加一个\0,叫做字符串结束标记(用来标记一个字符串的结束,为了测定一个字符串的实际长度,c语言还规定了
// //一个字符串结束标记,即 \0)
//
// //如果一个字符串,他的第10个字符为'\0',则此字符串的有效字符为9.但占内存还是要算的("\0")
//
//char e[1000] = "i am happy" ;
//char* p = "";
//char c[] = {'i',' ','a','m',' ','h','a','p','p','y','\0'};//与下面的等价
//char d[] = { "i am happy" };
//char c[] = "china";
//printf("%s \n",c);
//a)printf输出的字符不包括\0,(字符串结束标记)
//b)%s对应的是字符数组名c
//c)即便数组的长度大于字符串实际长度,也只输出到\0结束
//char c[1000];
//scanf("%s", c);//scanf("%s",&c);在商业中不常用
//printf("%s\n",c);//z
//在C语言中,一维字符数组就可以看成字符串常量
//字符串处理函数
//puts(字符串数组):将一个字符串输出到屏幕(能换行),注意只能输出一个字符串
//char str[100] = "are you ok";
//puts(str);//
//(2)gets(字符数组名):从键盘输入一个字符串到字符数组中,只能输入一个字符串,
//(3)strcat(字符串1,字符串2);
//(4)strcpy(字符数组1,字符串2),常用函数,必须用心掌握,字符串复制,必须用这个
//strcmp(),常用于比较相等或者不相等,一般不用于比较大小
//strlen(),返回的是字符串的实际长度,但不包括\0,返回的单位是字节数
//函数的基本概念和定义
//a,一个文件里面有一个或者多个函数组成。这个文件我们一般就称为源程序文件
//b.在大项目源程序不能都放一个文件里,行太多了,所以一个c项目是由一个或者多个源程序文件组成。
// 这些函数可以分别放到这些源程序文件里并被所有的源程序文件公用。
//c.c程序从main函数开始执行,最终也是在main函数里面结束整个程序。
//d.函数不能嵌套,不能如下述这样,不要手工调用main函数,这是留给系统去调用的
/*
void printfhello()//自定义的函数
{
printf("dd");
void printhello2()
{
printf("hello,how are you");
}
}
*/
//e.函数分两类,1.库函数,如printf函数,2.自定义函数,我们自己写的
//1.函数定义的第一行没有分号;
//2.形式参数在函数调用之前并不分配内存,调用的时候分配内存,函数调用结束后,形式参数的内存就被释放了,所以形式参数只能在函数内部使用。
//3.实参,可以是常量,变量,表达式
//4.形参数量和类型与实参数量和类型要保持一致。
//5.c语言规定,实参变量,对形参变量的数据传递是“值传递”,也就是单向传递,只由实参传递给形参,不能由形参传递给实参。
//函数调用方式及嵌套调用
//函数调用
//函数声明,必须把函数声明放在任何源代码文件的具体函数之前(一般也就是源代码的开头,才能保证这些具体函数调用,调用其他函数时,这些被调用的函数是声明过的)
//调用栈(一块系统分配给咱们这个程序的有特殊用途的内存):把形式参数,函数调用关系,局部变量,这段内存是有限的,如果一旦超过这个内存的大小,,就会出现崩溃现象
//递归调用
//范例:计算5的阶乘,其实就是计算1*2*3*4*5
//不知道5的阶乘是多少,但是知道4的阶乘乘以5就是5的阶乘;
//不知道-的阶乘是多少,但是知道-的阶乘乘以-就是-的阶乘;
//不知道-的阶乘是多少,但是知道-的阶乘乘以-就是-的阶乘;
//不知道-的阶乘是多少,但是知道-的阶乘乘以-就是-的阶乘;
//不知道2的阶乘是多少,但是知道1的阶乘乘以2就是2的阶乘;
//我们知道1的阶乘。
//递归调用的出口肯定是在1的阶乘,
/*
int dg_jiecheg(int n)
{
int result;
if (n == 1)
{
return 1;
}
else
{
result = dg_jiecheg(n - 1) * n;
}
return result;
}
*/
//递归
//优点:代码少,代码看起来简洁;
//缺点:1. 但不好理解;2.调用层次太深,调用栈(内存)可能会溢出,如果出现这种情况,那么说明不能用递归解决问题,
//3.效率和性能都不高,深层次的调用,要保存的东西很多,所以效率和性能高不起来,有些问题必须要用递归,汉诺塔
//递归函数的直接调用:
//1.调用递归函数f的过程中,f的有调用自己,这就是直接调用。
//2.递归函数间接调用,调用函数f1的过程中要调用f2的函数,然后f2函数过程中又要调用f1函数
//1.数组元素作为函数实参
//int a[10];//相当于定义了10个变量,a[0]-a[9];那么数组元素,就是a[0]-a[9],数据元素大家就可以当成变量使用;数组元素当整型变量一样使用,
//2.数组名作为函数实参
//实参和形参个数要相等,类型要一致,按顺序对应,一一传递。
//强调:数组名作为函数参数时,不是值传递,不是单向传递了,而是把实参数组的开始地址,传递给了形参数组,这样两个数组就会共同占用一段内存,
//这个其实就是地址传递,这个就是地址传递,,也就是说,形参数组中的各个元素如果发生了变化,会导致实参数组元素的值也发生相应的变化 ,这一点,是与变量做函数参数明显不同的
// 形参数组大小可以不确定,即便指定了也可以与实参数组大小不一致******,因为c编译器对形参数组大小不做检查***重点,只是将实参数组的首地址传递给形参数组
//用多维数组名作为函数实参和形参,形参在定义时,可以指定每一维的大小,也可以省略第一维的大小,但不能省略第二维大小。
//大家记住一点:实参是这些行这些列,你形参就尽量跟实参一样(也是这些行这些列),这样实参都能引用的下标形参就可以引用,就会保证你不会出错,
//大家只要理解 传递地址 这么一个概念去理解数组作为函数实参,就很简单
//局部变量和全局变量
//局部变量,1.函数内部定义变量。2.不同函数可以使用相同的变量名,互不干扰。3,形参也是局部变量,
//4.有一种特殊写法,虽然大家不这样写,但要记住他,
//在C语言中,允许直接出大括号,写一个复合语句定义变量,这种复合语句也叫程序块。
/*
{
int c;
c = 15;
}
*/
//全局变量:在函数外部定义的变量我们就称为全局变量(外部变量)
//优点:增加了函数与函数之间的数据联系渠道,如果一个函数中改变了全局变量的值,就能影响到其他函数,相当于在各个函数之间有了直接传递通道,不再需要
//通过实参和形参来传递参数值了
//缺点:a.只有在必要的时候才使用全局变量(要谨慎使用),因为全局变量在程序运行整个周期都占用着内存。而不像函数内的局部变量
//(函数内部局部变量的特点是当函数执行完毕后,这些局部变量所占的内存会被系统回收)
//b.降低了函数的通用性,因为函数执行时要依赖这些外部的全局变量了;如果将函数迁移到另外一个文件中,那这些相关的外部变量就得一起移植过去,并且如果
//你迁移到的另外一个文件中也有同名全局变量,那就更麻烦
//c.东一个全局量,西一个全局量,降低了程序的清晰和可读性。读程序的人难以清楚的判断每个瞬间各个外部变量的值。(因为很多函数都能够改变外部变量的值)
//所以,要限制使用全局变量
//说明:
//1>如果某个函数想引用在他后面定义的全局变量,则可以用一个关键字extern做一个"外部变量说明",表示该变量在函数的外部定义,这样函数内部就能使用
//否则编译就会出错,所以,全局变量(外部变量)的定义放在引用他的所有函数之前,就可以避免使用这个extern.
//2>要严格区分外部变量定义和外部变量说明:外部变量定义只能是一次,位置是所有函数之外,位置是所有函数之外,定义时分配内存。定义时可以初始化值,
//而同一个文件中,外部变量说明可以是很多次的(不分配内存),刚才老师演示的是把外部变量放在文件最上面,所有函数之外,可以在每个函数之内做外部变量说明
//这也是可以的,外部变量说明的理解:所声明的变量是已在外部定义过的变量,仅仅是引用该变量而做的声明
//下面这样方可(在main.c之外)
//extern int c1, c2;//外部变量说明(不分配内存),表示某一个地方定义了c1,c2,这两个全局量
//void lookValue()
//{
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2;//全局变量定义(定义了所以分配内存) //如果只写上面的,将这行注释掉的话,骗系统的话,系统也会报错,所以此种模式下,就会报错
//或者
//int c1 = 1, c2 = 2;
//void lookValue()
//{
// c1 = 5;
// c2 = 8;
// return;
//}
//所以,结论是全局变量的(外部变量)的定义放在引用他的所有函数之前,就可以避免使用这个extern
//void lookValue()
//{
// extern int c1, c2;
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2; 只在这里面弄也可以
//void lookValue()
//{
// extern int c1, c2;
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2;
//void lookValue2()
//{
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2; //这种情况不可以
//void lookValue()
//{
// extern int c1, c2;
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2;
//void lookValue2()
//{
// extern int c1, c2;
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2; //这种情况不可以
//extern int c1, c2;
//void lookValue()
//{
// //extern int c1, c2;
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2;
//void lookValue2()
//{
// //extern int c1, c2;
// c1 = 5;
// c2 = 8;
// return;
//}
//int c1 = 1, c2 = 2; //这种情况不可以
//(3)在同一个源文件中,如果全局变量和局部变量同名,则在局部变量作用范围内,全局变量不起作用(当然职业不会收影响)。
//变量的存储和引用,内部和外部函数
//1.变量的存储类别 (上次是从作用域来定的)
//从变量的存在的时间(生存期)角度来划分,我们把变量划分为:静态存储变量 和 动态存储变量,从而引出静态存储方式和动态存储方式,
//静态存储变量:在程序运行期间分配固定的存储空间的变量。 这种分配变量的方式就叫做静态存储方式;
//动态存储变量:在程序运行期间根据需要进行动态分配存储空间的变量。 这种分配变量的方式就叫做动态存储方式
/*
程序代码区
静态存储区:全局变量放在这里(在程序执行过程中它们占据着固定存储单元,而不是动态分配和释放)
动态存储区:1,函数形参。(函数形参被看作是局部变量)。2.局部变量,3.函数调用时现场的一些数据和返回地址。
*/
//数据存储在静态和动态存储区中,存储区就是内存
//如果你两次调用同一个函数,分配给此函数的局部变量等等的存储空间地址可能就是不同的
//二,局部变量的存储方式
//a).传统情形:
//函数的局部变量:函数被调用时分配空间,函数执行完成后自动释放其所占用的存储空间;
//b).特殊情形:
//局部静态变量:用static加以说明:能够保留原值,占用的存储单元不释放
//局部静态变量说明:
//a)在静态存储区内分配存储单元,程序整个运行期间不释放
//b) 局部静态变量是在编译时赋予初值的,只赋予初值一次,在程序运行的时候,它已经有了初值。以后每次调用函数不再重新赋予初值,二还是保留上次的函数调用结束值
//(普通局部变量的定义和赋值是在函数调用时进行的);
//c)定义局部静态变量时,如果不赋予初值,则编译自动赋初值为0值。而普通的局部变量,如果不赋初值,则该普通局部变量是一个不确定的值。
//d).虽然局部静态变量在函数结束后依然存在,但其他函数是不能引用他的。
//e).缺点:长期占用内存,降低了程序可读性
//结论:除非必要,否则不要多用局部静态变量。
//全局变量跨文件引用
//extern 做一个外部变量说明
//在引用该全局变量的文件中的头部做一个
//在引用该全局变量的文件中的头部做一个“外部变量说明”就可以,说明这里出现的变量是一个已经在其他文件中定义过的外部变量,本文件不必要再为他分配内存了
//作说明,在本文件或者其他文件中只要在一个project1中就可以,用extern int g_a;作说明就可以,如在a.cpp中定义的,b.cpp中用这个extern后,就可将g_a的作用范围扩展
//也可拓展到project3,4,5,6文件,由你来弄,这种必须放某一个文件的开头,所有函数的外面
//跨文件的这种,如果其他地方改变了值的话,那么回到源文件之后,就改变了,所以这个是有跨文件影响的
//在定义全局变量时前面增加static,则该全局变量只能在本文件中使用。
//四:函数的跨文件调用
//我们根据能否被其他源文件调用,我们将函数分为内部函数和外部函数
//内部函数:只能被本文件中其他函数调用。定义内部函数的时候,在函数定义最前边加一个static,形式如下:
//static 类型标识符 函数名(形参表){...} 这种内部函数又称为"静态函数",使用内部函数,可以使函数只局限与所在文件
//外部函数:如果一个函数定义,你不用static,他就是外部函数,当然,你可定义该函数时,在其前边添加一个extern,但默认就是extern
//extern 类型标识符 函数名(形参表){...}
//就是和上面外部变量相同,在其他文件中用如 extern void lookvalue2();,,
//外部函数前面可以省略这个extern,但外部变量前面就不可以
//五:static关键字用法总结(要记住这几种)
//(1)函数内部定义一个变量的时候使用static,则该变量会保存在静态存储区,在编译的时候初始化,如果你不给初始值,他的值会被初始化为0,并且下次调用该函数的时候,保持上次离开该函数时的值。
//(2)在全局变量之前增加static,会导致该全局变量,只能被本文件引用,无法再被其他文件中引用
//(3)在函数定义之前增加static,那么会导致该函数只能在本文件中被调用,无法再其他文件中调用。
//第八章 编译预处理
//一,带参数的宏定义
/*
一个项目可以通过编译,链接最终形成一个可执行文件。
每个源文件(.cpp),都会单独编译,编译成一个目标文件(.o,也可能是.obj,扩展名跟操作系统有关)
系统把这些.o文件进行链接,最终形成一个可执行文件
编译干了什么事?笼统的说:此法,语法分析,目标文件.o/.obj的生成,优化之类的。这个编译我们可以拆开来,看它干了几个事情,一般会干这么几个事情:
1.预处理:
2.编译:词法,语法分析,目标代码生成,优化,产生一些临时文件。
3.汇编:产生.o(.obj)目标文件。
预处理是干什么的?
咱们在源程序.cpp中加入一些特殊的代码(特殊的命令),这些特殊代码有一些特殊的能力,提供一些特殊功能。
编译系统会先对这些特殊代码做处理,这个就叫做"预处理",处理结果再和源程序代码一起进行上边的 2编译,3汇编这一系列操作
c语言一般提供三种预处理:1.宏定义,2.文件包含,3.条件编译。本节将宏定义
这三种功能也是通过在程序代码中写代码来实现,只不过这些代码比较特殊,都是以#开头。
一:不带参数的宏定义
不带参数的宏定义是干嘛的?用一个指定的标识符来代表一个字符串;一般形式
#define 标识符/宏名 字符串 //标识符也叫宏名
#define PI 3.1415926 //不带参数的宏定义:用PI来代替“3.1415926”这个字符串,程序源码中,我们在写的是PI,在编译预处理时,所有后在该#define之后的PI都会被替换成“3.1415926”
好处:
a)用一个简单的名字代替一个长字符串,所以这个标识符也被称为宏名,在预编译时将宏名替换成字符串的过程叫“宏展开”,#define就是宏定义命令
b)增加了修改的方便,为修改提供了极大的便利。这也叫增加了程序的可移植性。
说明:
1.宏名一般都用于大写字母,这是一种习惯,建议大家遵照这个习惯
2.宏定义不是C语句,所以不必在行末加分号,如果加分号则连分号会被一起替换。
3.#define命令出现在程序中函数的外面(一般在.cpp文件的最上面),宏名的有效范围是#define之后,到本源文件结束,不能跨文件使用,
4.可以用#undef命令终止宏定义的作用域,如#undef PI
5.用#define进行定义时,还可以引用已经定义的宏,可以层层置换.
6.字符串内的字符,即使与宏名相同,也不进行替换
char stmp[100] = "DPICPI";//比如说之前已经定义了DPICPI宏
二.带参数的宏定义
一般形式如下:
#define 宏名(参数表)字符串 :也是用右边“字符串”代替“宏名”(参数表)。
总结:带参数的宏定义是这样展开置换的;
对一般形式中这个“字符串”,如果字符串中有宏名后列出的参数 比如(a,b),则将程序语句中相应的实参(常量,变量,表达式)代替形参,
如果字符串中的字符不是参数字符,则保留。
#define S(a,b) a*b
a>如果输入
area = S(1+5); //在形式参数外面加一个括号就行了
b>宏定义时宏名和带参数的括号之间不能加括号,否则,空格以后的字符都作为替代字符串的一部分
如这个是不可以的 #define S (r) PI * (r) * (r)
宏定义和函数的区别
1.函数调用时先求出实参表达式的值,然后带入形参,带参数的宏只进行简单的字符替换,并没有求表达式的值等行为发生如 上面的S(1+5),没先加1 + 5;
2.函数调用时在程序运行时处理,分配临时内存,宏展开是在编译时进行的,展开是不分配内存,也没有返回值的说法,也没有值传递的说法。
3.宏的参数没有类型这个说法,只是个符号,展开时带入指定字符串中;
4.使用宏次数多时,宏展开后源程序程序变长,函数调用则不会
5.宏替换只占用编译时间,不占用运行时间,而函数调用占用的是运行时间(分配内存,传递参数,执行函数体,返回值)
宏替换复杂语句
#define MAX(x,y) (x) > (y) ? (x):(y)
宏替换多行语句
#define MACROTEST do { \
printf("test\n"); \
} while(0)
代码中写的时候就是直接写这个就行 MACROTEST;,如果定义宏的时候写了加了分号的话,那么在代码中写的时候就不加分号,直接写MACROTEST
//文件包含和条件编译
一:文件包含(编译中的预处理中的文件包含这一块)
将另外一个文件的内容包含到本文件中,我们通过#include命令来实现
一般为 #include "文件名"
#include常用于#include其他.h文件。.h文件称为头文件,h(head),我们常把宏定义,函数说明,甚至一些其他的#include命令,以及其他一些全局变量的外部声明放在头文件中,
在vs中你可以添加现有项,头文件,也可以不用添加,在对应.cpp文件里面加上就行
说明:
1)很多 公共修改都可以放到.h中,但是你一旦修改了这个.h文件,也就相当于你修改了#include这个 头文件的.cpp文件,那么这些.cpp文件,那么这些.cpp文件会对其进行重新编译。
2)一个#include只能包含一个文件,如果要包含多个文件,那就要写多个#include;
3)文件包含是可以嵌套的.一个头文件中还可以#include其他头文件,#include本质上就是把另外一个文件中的内容,搬到本文件中来,
二.条件编译(几种形式)
//形式1:当标识符被定义过(#define),则对程序段1进行编译,否则对程序段2进行编译。#else这部分可以没有
#ifdef 标识符
程序段1(一维代码)
#else
程序段2
#endif
#define DEBUG 如果不用宏的值的话,那么这样定义也行,就可用上面的这个
#形式2:
#ifndef 标识符 当标识符没有被定义过(#define),则对程序段1进行编译,否则对程序段2进行编译。
程序段1(一维代码)
#else
程序段2
#endif
#形式3:当指定表达式为真(非0)时就编译程序段,否则编译程序段2.我们事先给定一定的条件,就可以使程序在不同条件下实现不同的功能
#if 表达式
程序段1(一堆代码)
#else
程序段2
#endif
a.cpp-------------编译------------|
b.cpp-------------编译------------|链接=========>可执行文件
c.cpp-------------编译------------|
条件编译的好处
1.可以减少生成的目标文件的长度,如下,在PI已定义的情况下,只有打印xx的一行有效,哈哈
#if PI
printf("xx\n");
#else
printf("gg\n");
#endif
2.大家以后开发面临跨平台问题,在linux下开发的,在Windows下也可以开发,为了增加程序的可移植性,增加程序的灵活性,我们就必须采用这些条件编译
#if _WIN32 //放到windows之后,就会自动认出来,(将鼠标放上去就可以显示1了,鼠标放到_Win32,在wins上,指到__linux__,就不会显示了,这些带下划线的这些宏,都是相对应系统自己定义的,所以指到对应就能显示出来)
//WaitForSingleObject()
//Windows上专用
#elif __linux__
//epoll()
#else
#endif
*/
//第九章 指针
//一:总述
/*
1.前提知识
//静态存储区,动态存储区 --------不同变量会保存在不同的存储区
//a)有些变量的内存,是在编译的时候分配的
//b)有些变量的内存,是在程序运行时分配的
//但不管怎样说,变量都是会占用一段内存空间的;
//int,float,double,char,都占用一定的内存空间的。sizeof(类型)
2.地址的概念
在计算机中,是用一个数来描述一个地址。比如1000(十进制数);但是,计算机更习惯用16进制数的形式表示一个地址 ,内存位置
严格区分地址和地址中内容的区别
程序内部会维持着一张表
int i = 5,j = 6;
1000-------i
1004 ------j
(三)直接访问和间接访问
直接访问:按变量地址存取变量值,这个就叫做直接访问
间接访问:将变量i的地址,存放在另一个内存单元中,
在C语言中,我们一般定义int,char,float,double这些变量,我们一般是用这些类型的变量来存"值"。
在C语言中,我们也可以定义一种特殊的变量,这种特殊的变量用来保存地址的。
mypoint = &i; 把变量i的地址保存到了mypoint.
理解成myponint指向了i,这里所谓指向,就是通过地址体现
print("i = %d\n",i);//直接访问
如果我们现在要存取i的值。我们就有了一种间接的访问手段
a,先找到存放i的地址的这个内存位置(3000-3003),也就是mypoint的内存地址,从这四个字节的内存中,取出i的地址,因为我们知道mypoint它里面保存的是
整型变量的i的地址,整型变量是占4个字节,这表示1000这个地址开始的4个字节是个整型数据,那我们就从1000开始取得四个字节数据,也就是这个5,这就是i的值;
确定一个概念:
指针变量:如果一个变量,比如mypoint,专门用来存放另外一个变量的地址,则称这个变量为“指针变量”,这成为指针变量
指针变量的值(也就是其中存放的值)是个地址(也叫指针)
区别好 指针变量和“地址/指针”两个概念。指针就是个地址(地址也用数字表示)
指针变量是存放其他变量地址的变量,也叫该指针变量指向某某变量
第二节 变量的指针和指向变量的指针变量
一.指针变量的定义
变量的指针 就是变量的地址。我们可以定义一个指向变量的指针变量。这种指针变量,我们在定义的时候,会引入一个*号在定义的语句中
说明:
(1)大家注意,我“定义”指针变量的时候,指针变量前是有"*"的,表示这是正在"定义一个指针变量",但是,我用指针变量,指针变量名前是没有*号的,所以,指针变量名是mypoint1,mypoint2,不是*mypoint1,*mypoint2;
(2)一个指针变量只能指向同一个类型的类型的变量(普通变量),如果做错的话,否则出现编译错误。
二.指针变量的引用
牢记: 指针变量只存放地址。不要将一个整型变量赋值给一个指针变量;
和指针变量相关的运算符有两个:
(1).&:取地址运算符,大家都见过了,scanf
(2).*:指针运算符(间接访问运算符),1.乘法运算符 3* 4,2.定义指针变量的时候,我们用到*,这个叫指针运算符;3.但是若这个*不出现在指针变量定义的场合,那么这个*依旧是指针运算符
只不过代表的是该指针变量所指向的变量
解析开始:p是指针变量,a是普通变量 p指向a;
1. &*p: &比*的优先级要低,地址运算符合指针运算符都是自右至左结合的,*p指代的是p1所指向的变量,其实就是a,再执行&运算符,这样的话&*p,等价于&a;他本身不就是p;相当于啥都没干;
2.*&a,等价a
3.(*p)++;就是a++;
*p++;因为++和*同级,又是从右向左结合,所以*p++等价于*(p++);
大家务必知道p ++;
如原来指向的是整型变量,那么++之后就跳的是四个字节,那么原来p指向的是拥有的地址,那么++之后就不是拥有的了
//如果是普通整型变量i,那么i++,i如果是4,,那么i++是5,就是i这个变量对应的内容中的内容加1;
//那同理,指针变量自加1,也肯定是这个指针变量中的内容,也肯定是这个指针变量中的内容要自加,但是这种本来是自加1的操作,对于指针来讲,就不一定是自加1,自加几,取决于,
//该指针变量所指向的变量类型,
*p++,这个比较危险,因为这样会指向到不属于你的内存,如果再在改变后的指针位置赋值的话,很危险,就会把不属于你的内存的值覆盖掉
//指针变量作为函数
函数的参数,可以是指针类型,作用是一个变量的地址传送到一个函数中去。具体讲解的没有记,看视屏就行,记脑里了,
c语言中实参,
这个是不对的,如下:
int *p;
*p = 5;这个是不对的,*p代表指针变量p所指向的变量,但p到底指向谁呢?不确定,所以*p可能会造成某个内存被无意修改。从而是系统 崩溃
int *p;
int a;
p = &a;
*p = 5;
//数组的指针和指向数组的指针
数组指针:是指 数组的开始地址,数组元素的指针就是数组元素的地址
数组元素的内存一定是挨着的
p= &a[0];//将a[0]元素的地址赋给指针变量p,即p指向数组第0号元素;数组名代表数组的首地址
int *p = &a[0];
二:通过数组指针引用的元素
//请大家仔细看下面的效果,大家要花点心思看
a) *p = 19;//a[0]= 19;
b) p = p + 1;//增加了4,比如原来p = 1000, p = p + 1后 p = 1004;
c)p + i;或者 a + i是啥意思(i是数组元素的下标)
p + i或者 a + i就是数组元素的a[i]的地址,也就是说,他们指向了数组a的第i个元素 p + 3, a+3,a+ 3*4
//p + i 或者 a + i,都是地址,既然是地址,就可以赋给指针变量,
d)*(p + i)或者*(a+ i) <====>a[2]
引用数组元素的多种方法
a[i],p[i],*(p + i),*(a + i)
注意事项
1.a++
2.&a[5],*(a + 5)
f)*p++,等价于*(p++
g)*(++p)等价于*(++p),++p是先加后用;
//数组名作为函数参数
1.实参和形参都是数组名
a是实参数组首地址,那么ba是形参数组首地址,a和ba共用一段内存,,也即是说,在调用changevalue期间,a和ba指的是同一个数组
int a[5];
a[0] = 3;a[1] = 4;a[2] = 78;a[3] = 89;a[4] = 79;
changevalue(a);
在main函数之外的函数可以写成:
void changevalue(int ba[])
{
ba[1] = 75;
ba[2] = 56;
return;
}
(2)实参用数组名,形参用指针变量
void changevalue(int *p)
{
*( p + 2 ) = 888;
return;
}
(3)实参和形参都用指针变量,
int *p = a;
void changevalue(int *pa)
{
*( pa + 2 ) = 888;
return;
}
(4)实参为指针,形参为数组名
这个比较少见,诡异,哈哈
//第五节 数组的指针和指向的数组的指针变量
一.回顾二维数组和多维数组的概念
int a[3][4];
int a[3][4][5];
二.指向多维数组的指针和指针变量的研究
可以把a看成是一个一维数组,这个一维数组有三个元素:a[0],a[1],a[2],每个元素又是一个包含4个元素的一维数组,那前面这三个就代表一维数组的首地址
1)二维数组名,也是整个二维数组的首地址。我们可认为是第0行的首地址是1000
*/
int a[3][4];//数组名a同样代表数组的首地址
int *p;
p = (int *)a;
/*
2)a+1,a+2分别代表第一行首地址,和第二行首地址,所以a+1是要跳过16个字节的。
3)这表示a[0],a[1],a[2]是一维数组名。c语言规定数组名代表数组的首地址,,所以就有如下:
//a[0] == &a[0][0] == 1000 是第0行行元素首地址
a[1] == &a[1][0] == 1016 是第1行行元素首地址
............
4) 第一行第一列元素地址怎样表示
//&a[0][1],a[0]+1,
结论:
*(a+i)等价于a[i];
神奇的内容
a和*a,都是地址,且地址值相同,a+1和*(a+1),a+2和*(a+2)也是同理,a[i]和&a[i]都是地址,而且地址值也相同;
5)a[0]等价于*a,地址值相同,
a[0] + 1 等价于*a +1,也等价于&a[0][1]
a[1]等价于*(a+1),
a[1]+2,等价于*(a+1)+2,等价于&a[1][2]
6)*(a[0]+1)就代表a[0][1]的值;*(*a + 1)也是a[0][1]的值;
a[i]和&a[i]的地址值相同
三.指针数组和数组指针
int *p[10];//首先这是个数组,数组中有10个元素,每个元素都是个指针,所以这定义了10个指针变量
p[0] = &a[0][0];
数组指针:数组指针本身不太好理解,用的也不算多,大家能掌握的就尽量掌握,至少熟悉基本概念
int (*p)[10];//这是一个指针变量,这个指针变量用来指向含有10个元素的一维数组
int a[10];
p = &a;//a和&a其实值应该相同。,注意这里要用&地址符,否则编译报错
int b[2][3];
p = b;//二维数组名可以直接赋值给数组指针 *(*(p + i) + j)
四:多维数组的指针做函数参数
字符串的指针和指向字符串的指针变量
一:
二:字符串指针做函数参数
while(*from)
{
*to++ = *from++;//b[i] = a[i];之后 i++;
}
*to = '\0';
三.字符指针变量与字符数组
1.字符数组时由若干个元素组成。每个元素中存放一个字符,而字符指针变量中存放的是字符串的首地址。仅仅是首地址,大家千万不要理解为字符串内容放到字符指针变量中
2.赋值方式;
*/
//char str[100] = "I Love China";//定义初始化,相当于拷贝字符串内容到str中
str = "I love China"; 这个是错的
// strcpy(str,"I love China");//"I love china"是字符串常量,在内存中是有固定地址的,这里的赋值知识让字符指针a指向这个地址而已
//3.指针变量的值可以改变
/*char *a = "I love china";
a = a + 7;
printf("%s\n",a);*/
/* 但是字符数组的
char a[] = "I love china";
a = a + 7;//数组首地址是不可以变的
第八节 函数指针和返回值指针值的函数
1.用函数指针变量调用函数
一个函数,在编译的时候,系统会给这个函数分配一个入口地址。这个地址就称为函数的指针(地址)。
既然有地址,那么我们可以定义一个指针变量指向该函数,然后,我们就可以通过该指针变量调用该函数了。
每个函数在可执行文件执行时,都会占用一段内存单元,他们有一个起始地址。既然有地址,就可以用一个指针变量指向一个函数,
int c;
//c = max(5,19);
int (*p)(int x,int y);
p = max;//将函数max的入口地址赋值给指针变量p,函数名代表函数的入口地址。
c = (*p)(5,19);//调用*p就是调用函数max。p指向函数max的入口。这里*p指代了函数名。这种情况下,不可以写成int *p(int x,int y);函数名代表函数的入口地址
有()就确保了*和p先结合,
也可以写成p = &a;,
c = (*p)(5,19);//调用*p就是调用函数max.p指向函数max的入口,等价于c = max(5,19);
//这里的调用只是用*p取代了函数名max,
//p不能指向函数中间的某条语句,所以*(p + 1)不合法,
//其实这个*是可以省略的;所以在这里c= (*p)(5,19)和c= p(5,19);
总结:
a)函数指针变量定义的一般形式:
//数据类型标识符(*指针变量名)(形参列表);//其中“数据类型标识符”就是指函数的返回值类型,"形参列表"里可以只有类型说明符,多个类型说明符之间用逗号分隔
我们可以通过函数指针指向不同的函数来达到调用不同函数的目的。这个是有实际用途的。
b)函数的调用,可以通过函数名,也可以通过函数指针调用。
c)对指向函数的指针变量p,做一些像p++,p--,等运算是不可以的,也没有意义;
二.把指向函数的指针变量作为函数参数;
//指向函数的指针变量也可以作为另外一个函数FuncB的参数,从而实现函数地址的传递,也就是在FuncB函数中调用该函数指针变量所指向的函数的目的。
int wwmax(
int x,
int y,
int (*midfunction)(int x,int y)//形参就是一个函数指针
)
{
int result = midfunc(x,y);//调用函数指针midfunc所指向的函数
return result;
}
则此时
三.返回指针值的函数
//return; return 1;
//函数中也可以返回指针型数据。也就是地址;
//返回指针值的函数的一般定义形式:
数据类型 *函数名(参数列表)
int *a(int x, int y);--->a 函数名,()优先级高于*,因此a先和()结合,这就是函数形式,返回值为整型指针
int *add(int x,int y)
{
int sum =x + y;
return ∑//隐藏一个致命的问题,add函数调用完毕后,sum的内存是被系统回收,绝对不可以把sum的内存地址返回到被调用函数中并加以利用。
} //但若将sum定义在外部作为全局变量,生存期一直到程序结束。
int *presult;
presult = add(4,5);//执行add后,presult指向内存,已经不归你所有,你不应该从中取得值或者给他赋值。
printf("和值为%d\n",*presult);
第九节 指针数组、指针的指针、main函数参数,小节
一。指针数组的概念回顾:一个数组,其元素均为指针类型数据,称为指针数组。指针数组中每一个元素都是指针变量。
指针数组的定义形式:
类型标识符 *数组名[数组长度说明
int *p[4];
数组指针 int (*p)[4];首先是指一个指针变量。
//二:指向指针的指针,用来指向指针的变量的变量,简称指向指针的指针;
char **p;
int **p;定义了一个指向整型指针变量的指针变量
//*(*p);表示指针变量p是指向一个指针变量,*p是p所指向的指针变量
三.指针数组做main函数形参
指针数组有个重要应用,就是可以做main的函数的参数,
*/
printf("断点停在这里\n");
return 0;
}
//void funcTest()
//{
//
// static int c = 4;
// printf("c = %d\n",c);
// c++;
// return;
//}