C语言
文章目录
一 什么是C语言
人和计算机交流的语言是计算机语言,C是一种通用的计算机语言,广泛使用于底层开发。
二 第一个C语言程序
1 写出主函数(main函数)
2 C语言是从主函数第一行开始执行,main函数是代码的入口
3 int是函数的返回类型 括号代表main是一个函数 大括号里的是函数体
4 printf 是库函数 要引用头文件才能使用(stdio.h)
5 一个工程中只能用一个主函数
#include <stdio.h>
int main()
{
printf("hello world");
return 0;
}
三 数据类型
1 数据类型 char(字符数据比如字母) 、short(短整型) int(整型)、long(长整型)、 long long(更长的整型)、float(单精度浮点数)、double(双精度浮点数)
2 如果想要查看数据类型的大小,具体操作是printf(“%d\n”,sizeof(数据类型));
3 sizeof也是一个函数 单位是字节
4 计算机中的单位:bit 比特位(一个二进制位占空间)byte=字节=8bit kb=1024byte mb=1024kb gb=1024mb tb=1024gb pb=1024tb 数据类型的划分可以提高空间利用率,根据数据大小选择数据类型。
#include <stdio.h>
int main()
{
//字符类型
char ch = 'a';
//整型
int age =20;
float weight = 55.5;
printf("%f",weight);
printf("%d",age);//%d就是打印一个整数
return 0;
}
四 变量、常量
1 常量:不能改变的量 变量:可以改变的量
%d - int %f - float %lf - double
#include <stdio.h>
int main()
{
//创建一个变量
//类型 变量的名字 = 0
int age = 20;
double weight = 30;
age = age+1;
weight = weight+10;
printf("%d\n",age);
printf("%lf\n",weight);
return 0;
}
2 局部变量和全局变量
a 在大括号内部定义的变量都被称为局部变量
b 在大括号外部定义的变量为全局变量
c 局部变量和全局变量名字冲突的情况下,局部优先,一般不把全局局部名字一样
3 两个整数相加
int main()
{
int a = 0;
int b = 0;
int sum = 0;
scanf("%d,%d",&a,&b);
sum = a + b;
printf("%d",sum);
return 0;
}
4 作用域和作用周期
a 局部变量的作用域是变量所在的局部范围
b 全局变量的作用域是整个工程,可以跨文件,需要加extern int 变量
c 变量的生命周期就是变量创建和销毁之间的时间段
1)局部变量:进入局部范围生命开始,出局部范围生命结束
2)全局变量:程序的生命周期
5 常量:字面常量 const修饰的常变量 #define定义的标识符常量 枚举常量
a 9,20,‘a’-字面变量
b const int num = 10 ->const修饰的常变量,具有常属性,但不是常量,本质上是变量
c #define MAX 10000 ->#define定义的标识符常量
d 枚举常量 可以一一列举的常量
#include <stdio.h>
enum Sex
{
//枚举类型的变量的未来可能取值,称为枚举常量
male = 3,//不算改了常量,此操作叫赋初值。
female,
secret,
};
int main()
{
enum Sex a = female;
printf("%d",a);
return 0;
}
//最终输出结果为4
五 字符串
1.用双引号括起来的一串字符
#include <stdio.h>
int main()
{
//字符数组 数组是一类形同类型的元素
//字符串末尾隐藏了一个\0的字符,\0是字符串结束的标志
//%s是打印字符串的意思 %c是打印字符的意思
char arr[] = "hello";
char arr1[] = "abc";
char arr2[] = {'a','b','c','\0'};//若没加后面的\0,则得不出abc 会是一个随机打印值
printf("%s\n",arr1);
printf("%s\n",arr2);
printf("%s\n",arr);
//求字符串长度 运用strlen()函数 string length
//要引入头文件才不会报错 这里是#include <string.h>
int len = strlen(arr1);
printf("%d\n",len);
printf("%d\n",strlen(arr2));
return 0;
}
2.转义字符:转变了原来意思的字符
常用的转义字符:\t:水平制表符tab,\n:换行,\r \f \v \b \a也是转义字符,同时\xdd,\ddd也是转义字符,具体的作用可以自己写代码尝试。下面的代码注释中也有相关作用描述。
#include <stdio.h>
int main()
{
//\t:水平制表符tab \n:换行 \r \f \v \b \a都是转义字符
printf("c:\test\328\test.c\n");//输出为c: est est.c
printf("ab\ncd\n");//输出为ab 换行cd
printf("%c\n",'\'');//在这里'也进行了转义,加上\让它仅仅成为一个单引号,而不是和另外一个单引号构成整体。
//printf("\a\a\a\a");//会让电脑产生蜂鸣
printf("%c\n",'\101');
//\ddd是转义字符,其中ddd是八进制数,101八进制转化为十进制为65,而65所对应的ASCII码是A,因此输出是A,还可以用其他字母进行验证。
//\xdd 的dd是十六进制数,作用和\ddd类似
printf("%d\n",strlen("c:\test\328\test.c"));
return 0;
}
六 分支语句(选择结构)
选择语句语法结构
字面意思就是当if语句括号内部的表达式成立即为真,则执行下面的语句。
//单分支
if(表达式){
语句1;
语句2;
语句3;
}//如果不加大括号,如果表达式成立,只会执行一句语句(语句1),加了大括号,可以执行语句列表(语句123)
//双分支
if(表达式)
语句1;
else
语句2;
//多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
双分支语句
#include <stdio.h>
int main()
{
int age = 19;
if(age>=18){
printf("成年了");//若age大于18,则执行这个语句
}
else{
printf("未成年\n");
printf("啦啦啦");
}
return 0;
}
#include <stdio.h>
int main()
{
int a = 0;
printf("你要好好学习吗(0/1)");
scanf("%d\n",&a);
if (a == 0)
{
printf("好好学习才能取得进步");
}
else
{
printf("努力一定会获得回报的");
}
return 0;
}
多分支语句
#include <stdio.h>
int main ()
{
int a =0;
scanf("%d",&a);
if(a<18){
printf("少年\n");
}
else if(a >= 18 && a<=26){
printf("青年");
}
else if(a>26 && a<=60){
printf("壮年");
}
else{
printf("老年");
}
return 0;
}
switch语句
基本形式
switch(整型表达式)
{
//语句项
case 整型常量表达式:
语句;
}
//switch括号内的表达式必须是整型类型变量
#include <stdio.h>
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("星期日");
}
return 0;
}
//注意,如果不加break,则输入为几,则从case几进入,并且运行完后面的所有代码
//case是进入的入口,break是switch语句的出口,如果没有break,则会一直向下运行。
int main()
{
int day = 0 ;
scanf("%d",&day);
switch(day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日");
break;
case 6:
case 7:
printf("休息日");
break;
default://除了case之外的输入
printf("输入不正确");
break;
}
return 0;
}
悬空else
else写出来造成了else与if的匹配问题时,就被称为悬空else。
**注意,当没有大括号作为分割时,else与最近的if对齐,而非根据缩进判断,编译器自动对齐,比如下面的代码例子。
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hehe\n");
else{
printf("haha\n");
}//else和最近的if对齐
return 0 ;
}
=>
//编译器自动写成
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hehe\n");
else{
printf("haha\n");
}//else和最近的if对齐
return 0 ;
}//因此输出为空
以上代码与下面的代码对比,上面代码不输出,下面代码输出haha。
int main()
{
int a = 0;
int b = 2;
if(a == 1){
if(b == 2){
printf("hehe\n");//TODO
}
//TODO
}
else{
printf("haha\n");
}
return 0 ;
}//和上面的代码对比,加了大括号后,输出为haha
重点:因此,在写分支语句时,最好是加一个大括号,表示一个部分,这样可以避免出现悬空else的情况
例子
1.判断是不是奇数
int main()
{
int a = 0;
scanf("%d",&a);
if(a%2 == 0){
printf("该数为偶数");
}
else{
printf("该数是奇数");
}
return 0 ;
}
2.输出1到100的奇数
#include <stdio.h>
//while循环
int main()
{
int num = 1;
while ( num >0 && num<=100){
if(num % 2 == 1){
printf("%d\n",num);
}
num = num +1;
}
return 0;
}
//for循环
int main()
{
int a = 0;
for(a = 1; a <= 100;a++){
if (a%2 ==1){
printf("%d ",a);
}
}
return 0;
}
3.三个数从大到小输出
int main()
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
if(a<b){
int d = a;
a = b;
b = d;
}
if(a<c){
int d = a;
a = c;
c = d;
}
if(b<c){
int d = b;
b = c;
c = d;
}
printf("%d %d %d",a,b,c);
return 0;
}
七 循环语句(循环结构)
1.while循环
//语法结构
while(表达式)
循环语句;
#include <stdio.h>
int main()
{
int line = 0;
while(line<10000)
{
printf("该学习了%d\n",line);
line++;
}
if(line == 10000){
printf("休息一下吧\n");
}
return 0;
}
continue与break
int main()
{
int i =1;
while(i<10){
if(i==5){
break;
}
printf("%d ",i);
i++;
}
return 0;
}
//在while循环中,break用于永久终止循环
//这个代码输出为1 2 3 4
int main()
{
int i =1;
while(i<10){
if(i==5){
continue;
}
printf("%d ",i);
i++;
}
return 0;
}
//在while循环中,continue用于跳过本次循环continue后面的代码,直接去判断部分,看是否进行下一次循环
//这个代码的结果是1 2 3 4然后死循环
//EOF-end of file-文件结束的标志 本质是-1
//getchar获取一个字符,当字符读取错误时,会返回EOF,读取字符时返回int类型 字符本身由ASC码
int main ()
{
int ch = getchar();
printf("%c\n",ch);
putchar(ch);//输出一个字符
return 0;
}
//循环
int main ()
{
int ch = 0;
while((ch = getchar()) != EOF){
putchar(ch);
}//ctrl+z可以结束循环
return 0;
}
2.for循环
语法
一般不建议在循环体内修改循环变量,防止for循环不受控制。
且一般不要省略表达式1,2,3。
特别注意表达式2,是判断表达式,不要写成赋值表达式。
for(表达式1;表达式2;表达式3){
循环语句;
}
//表达式1是初始化条件,表达式2是条件判断部分,表达式3是步进
//在for循环中,continue和break的作用相似
int main()
{
//当循环判断条件部分省略时,代码会进入死循环。
for(;;){
printf("\n");
}
return 0;
}
//为死循环。
int main ()
{
int i = 0;
int j = 0;
for(;i<3;i++){
for(;j<3;j++){
printf("hehe\n");
}
}
return 0;
}
//输出为三个hehe,而非九个,是因为i第二次进入循环时,j的初始值是3,这是由于第二层循环将初始化条件省去,j在外循环第一次时已经变成了3,再进一次循环,j仍然是3,不满足打印hehe的条件。
//因此,写for循环的代码时,最好不要随意省略,保证代码正确运行。
3. do…while循环
特点:循环至少执行一次,应用场景不多。
int main()
{
int i = 1;
do{
printf("%d ",i);
i++;
}while(i<=10);
return 0;
}
例题
1.n!
//计算n的阶乘
int main()
{
int n;
int c = 1;
scanf("%d",&n);
for(int i=1;i<n+1;i++){
c = c * i;
}
printf("%d",c);
return 0;
}
2.计算1!+2!+…+10!
int Jie(int n){
int c = 1;
for(int i=1;i<n+1;i++){
c = c * i;
}
return c;
}
int main ()
{
int a ;
scanf("%d",&a);
int b = 0;
for(int j=1;j<a+1;j++){
int d = Jie(j);
b = b + d;
}
printf("%d",b);
return 0;
}
3.在一个有序数组中查找具体的数字n
折半查找(二分查找)
int main ()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int k = 17;
int sz = sizeof(arr)/sizeof(arr[0]);
int left = 0;
int right =sz -1;
while(left<=right){
int mid = (left+right)/2;
if(arr[mid] > k){
right = mid + 1;
}
else if(arr[mid] < k){
left = mid + 1;
}
else {
printf("找到了:%d\n",arr[mid]);
break;
}
}
if(left >right){
printf("没有这个数");
}
return 0 ;
}
4.替换字符
#include <string.h>
int main ()
{
char arr1 [] = "like";
char arr2 [] = "####";
int left = 0;
int a = strlen(arr1);
int right = a-1;
while(left<=right){
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n",arr2);
left++;
right--;
}
return 0 ;
}
*好玩的从两边向中间显示字符
#include <string.h>
#include <windows.h>
int main ()
{
char arr1 [] = "nishidacongming" ;
char arr2 [] = "###############";
int left = 0;
int a = strlen(arr1);
int right = a-1;
while(left<=right){
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n",arr2);
Sleep(500);//休眠0.5s
system("cls");//清空屏幕
left++;
right--;
}
printf(arr1);
return 0 ;
}
5.系统输密码
*strcmp:比较两个字符串大小,判断是否一样,若str1与str2相同,返回0,str1小于str2,返回<0,str1小于str2,返回>0。
#include <string.h>
int main()
{
int i = 0;
char password[20] = {0};
for(i=0;i<3;i++){
printf("请输入密码:");
scanf("%s",password);//字符串不需要&,数组名不需要取地址
//if(password == "123456")//不能用==对两个字符串进行比较,应该用strcmp
if(strcmp(password,"123456") == 0){
printf("登录成功\n");
break;
}
else{
printf("密码错误,请重新输入 \n");
}
}
if (i == 3){
printf("三次密码错误,退出程序");
}
return 0;
}
6.猜数字游戏
//猜数字游戏
//自动产生一个随机数,玩家猜数字,猜对显示恭喜你,游戏结束,猜错,会告诉你猜大了还是猜小了,继续猜
//想玩可以继续玩,不想玩了可以退出
#include <stdlib.h>
#include <time.h>
void menu()
{
printf("1.play 0.exit\n");
}
void game(){
//生成随机数 rand函数 引用stdlib.h头文件
//rand函数返回0~32767之间的数字,rand()使用前需要srand函数
int ret = rand()%100 + 1;
//printf("%d",ret);
int guess = 0;
while(1){
printf("请猜数字");
scanf("%d",&guess);
if(guess<ret){
printf("猜小了\n");
}else if(guess>ret){
printf("猜大了\n");
}else{
printf("猜对了\n");
break;
}
}
}
int main()
{ //srand()里面数字相同,生成的rand值会相同
//时间 - 时间戳是一直变化的,因此srand里面可以放入时间戳
srand((unsigned int) time(NULL));//强制转化成int类型
int input = 0;
do{
menu();//打印菜单
printf("请选择是否进行游戏:1/0\n");
scanf("%d",&input);
switch (input) {
case 1:
printf("猜数字\n");
game();
break;
case 2:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择:\n");
break;
}
}while(input);
return 0;
}
7.经典 辗转相除求公因数
//最大公约数 普通方法
int main()
{
int m,n;
scanf("%d %d",&m,&n);
int k ;
if(m>n){
k = n;
}
else{
k = m;
}
while(1){
if(m%k == 0 && n%k == 0){
printf("%d",k);
break;
}
k--;
}
return 0;
}
//辗转相除法
int main()
{
int m,n,k;
scanf("%d %d",&m,&n);
while(m % n != 0){
k = m%n;
m = n;
n = k;
}
if(k != 1){
printf("最大公约数为:%d",k);
}
else{
printf("没有最大公约数");
}
return 0;
}
八 函数
子程序:函数 大型程序中部分代码
函数有输入参数和返回值
C语言函数包括库函数和自定义函数
一.库函数
使用库函数都要引头文件
库函数可以在以下链接可以查询,进行学习:
https://cplusplus.com/reference/
msdn
库函数包括以下几个形式:
1.IO函数 printf scanf getchar putchar
2字符串操作函数 strcmp strlen
3.字符操作函数
4.内存操作函数
5.时间/日期函数
6.数学函数
7.其他库函数
其中Add(x,y)就是一个函数。一般函数引用括号不能省略。
*strcpy(arr1,arr2) ----将arr2的内容copy,并导入arr1。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {0};
char arr1[] = {"arj"};
strcpy(arr,arr1);
printf("%s",arr);
return 0;
}
*memset ------ 把字符串的前几个字符改成同一个字符
//memset效果是把字符串的前几个字符改成同一个字符
int main()
{
char arr[] = "hello";
memset (arr , 'x' ,3);//memset(待改变量,'改成的字符',前几个字符进行改变)
printf("%s",arr);
return 0 ;
}
二.自定义函数
*当返回类型不写时,返回类型默认为int,不写返回类型不等于void。
ret_type fun_name(para1,*)
{
statement;//语句项 函数体
}
ret_type --- 返回类型
fun_name --- 函数名
para1 --- 函数参数
注意:当返回类型为void时,表示函数不返回任何值,也不需要返回。比如交换两个整型变量的函数,只需要交换两个变量,而不需要返回任何值。
a.函数的形参和实参
形参:函数名后面括号中的变量,形式参数只有在函数被调用的过程中才会分配内存单元,形式参数在函数调用完成之后就会自动销毁,形参只在函数内有效。
实参:函数调用时实际传入的参数,形式可以为常量、变量、表达式,函数,但是无论实参是何种类型的量,在进行函数调用时,都必须有确定的值,以便把这些值传给形参。
#include <stdio.h>
int Add(int a,int b)
{
int c = 0;
c = a + b;
return c;
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d,%d",&num1,&num2);
int sum = 0 ;
sum = Add(num1,num2);
printf("%d\n",sum);
return 0 ;
}
b.函数的调用
传值调用:实参和形参并没有联系,函数形参和实参分别占有不同的内存空间,形参的修改不会改变实参。
传址调用:函数外部创建的内存地址传递给函数参数的一种调用函数的方式,这种方式可以使函数和函数外边变量产生联系,函数内部可以直接操作函数外部的变量。
void Swap(int x,int y)//传值函数
{
int z = x;
x = y;
y = z;
}
void Swap1(int* pa,int* pb)//传址函数
{
int z = * pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
Swap(a,b);//传值调用
printf("%d %d\n",a,b);
//输出仍是a和b的原始值,并未达到交换数值的目的,原因是实参传给形参,形参只是实参的一份临时拷贝,并不会改变实参的值。
//若对a,b x,y取地址,可以发现,x与y只是分别与a和b相同的数字,占有独立的内存,x和y的改变不会影响a和b,因此不能达到交换主函数内数字的目的。
Swap1(&a,&b);//传址调用
//Swap1函数输入的是a,b的地址,通过指针可以改变主函数中a,b的值。因此这个函数可以实现交换功能。
printf("%d %d\n",a,b);
return 0;
}
*数组传参传给函数的是数组的首个元素的地址
//找到返回下标,找不到返回-1
int binary_search(int a[] , int b , int c)
{
int left = 0;
int right = c - 1;
int mid = (left+right)/2;
while(left<=right)
{
if(a[mid] > b)
{
right = mid-1;
}
else if(a[mid] < b)
{
left = mid+1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
int a;
scanf("%d",&a);
int sz = sizeof(arr)/sizeof(arr[0]);
int ret = binary_search(arr,a,sz);
//数组在传参的时候不会把整个数组传过去,只会传入首个元素的地址
//因此要在binary_search外部先将数组的大小算出来再传入函数
if(ret == -1)
{
printf("找不到");
}
else
{
printf("找到了,下标是:%d\n",ret);
}
return 0;
}
*传址调用实例
//调用一次函数,num就+1
void Dd(int* pa)
{
*pa = *pa + 1;
}
int main()
{
int num = 0;
Dd(&num);
Dd(&num);
Dd(&num);
Dd(&num);
Dd(&num);
printf("%d",num);
return 0 ;
}
***函数的嵌套调用和链式访问
1.函数不能嵌套定义,但是可以嵌套调用,比如
void test1()
{
printf("kae");
}
void test2()
{
test1();
}
int main()
{
test2();
return 0;
}
2.链式访问:把一个函数的返回值作为另一个函数的参数
#include <string.h>
int main()
{
printf("%d\n",strlen("abc"));
char arr1[] = {0};
char arr2[] = "yeye";
printf("%s\n",strcpy(arr1,arr2));
//strcpy函数的返回值是arr1。
printf("%d",printf("%d",printf("%d",43));
//输出为4321
//printf的返回值是打印在屏幕上的字符数量
}
c.函数的声明和定义
函数声明:告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是否存在,无关紧要,函数声明一般出现在函数使用之前,函数的声明一般要放在头文件中。
函数定义:函数的具体实现。
d.函数递归
将大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,其主要的思考方式为把大事化小。
必要条件:存在限制条件,当满足限制条件时,递归不再继续;且每次递归调用之后越来越接近这个限制条件。
1.按顺序打印一个数的每一位
#include <stdio.h>
#include <string.h>
void print(unsigned int n)
{
if(n>9)
{
print(n/10);
}
printf("%d ",n%10);
}
int main()
{
unsigned int num;
scanf("%d",&num);
print(num);
return 0;
}
2.stack overflow 栈溢出
内存:
a.栈区:局部变量 函数形参 调用函数时返回值等临时变量
b.堆区:动态内存分配的空间 malloc/free calloc realloc
c.静态区:全局变量 静态变量
//栈溢出典型例子
void test(int n)
{
if(n<10000){
test(n+1);}
}
int main()
{
int a = 0;
test(a);
return 0;
}
**递归代码在书写的时候 要注意不能死递归 递归层次也不能太深
3.编写程序不允许创建临时变量,求字符串长度
int my_strlen(char* str)
{
if(*str != '\0')
{
return 1+my_strlen(str+1);//str+1的意思是地址+1
}
else
{
return 0;
}
}
int main ()
{
char arr [] = "";
scanf("%s",arr);
printf("%d\n",my_strlen(arr));
return 0;
}
5.递归与迭代
1).阶乘
int jie(int n)
{
if(n<=1)
{
return 1;
}
else{
return n*jie(n-1);
}
}
int main()
{
int a;
scanf("%d",&a);
printf("%d",jie(a));
return 0;
}
2).斐波拉契亚数列
int Feb(int n)//递归,效率比较低
{
if(n == 1)
{
return 1;
}
else if(n == 2)
{
return 1;
}
else
{
return Feb(n-1)+Feb(n-2);
}
}
int Feb2(int n)//迭代,效率高
{
int a = 1;
int b = 1;
int c = 1;
while(n>2)
{
c=a+b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
while(1)
{
int n;
scanf("%d",&n);
printf("%d\n",Feb(n));
printf("%d\n",Feb2(n));
}
return 0;
}
3).汉诺塔求解
4).青蛙跳台阶
一次1或2阶,n个台阶多少跳法
5).n的k次方
double nk(int n,int k)
{
if(k>0)
{
return n*nk(n,k-1);
}
else if(k<0)
{
return 1.0/nk(n,-k);
}
else
{
return 1.0;
}
}
int main()
{
int a;
int b;
scanf("%d %d",&a,&b);
printf("%lf\n",nk(a,b));
return 0;
}
6).倒序字符串**
void Daoxu(char* arr)
{
int left = 0;
int right = my_strlen(arr) - 1;
char tmp = arr[left];
arr[left] = arr[right];
arr[right] = '\0';
if(my_strlen(arr)>=2){
Daoxu(arr+1);}
arr[right] = tmp;
}
int main ()
{
char arr[] = "";
scanf("%s",arr);
Daoxu(arr);
printf("%s\n",arr);
return 0;
}
九 数组
1.一维数组
//数组是一组相同类型元素的组合。一般写成arr[],其中[i]里的数字i代表数组里的第i+1个数。
//数组在内存中的存储是连续的,由低地址向高地址。
**数组名是数组首元素的地址。
//[ ]是下标引用操作符。
type_c arr_name[const_n];
//type_c 数组的元素类型 const_n 常量表达式,指定数组大小
//数组元素大小可以计算 大小 sz = sizeof(arr)/sizeof(arr[0]);
#include <stdio.h>
int main ()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
char ch[5] = {'a','b','c'};
printf("%d\n",arr[9]);
printf("%d\n",ch[0]);
int i = 0;
while(i<10)
{
printf("%d ",arr[i]);
i++;
}
return 0;
}
2.二维数组
int arr[][];//第一个[]:多少行 第二个[]:多少列
char arr[][];
double arr[][];
a.创建及初始化
初始化时行可以省略,但是列不能省略
#include <stdio.h>
int main()
{
//初始化
int arr1[3][4] = {1,2,3,4,5};//不完全初始化
int arr2[3][4] = {{1,2},{3,4},{5,6}};
int arr2[][4] = {{1,2},{3,4},{5,6}};
return 0;
}
b.二维数组在数组中的存储
二维数组在内存中也是连续存放的,一行内部连续,换行也是连续的。
c.数组作为函数参数
冒泡排序:两两相邻的元素进行比较,并且可能的话,需要交换顺序。
**数组名是数组首元素的地址,但是有两个例外,sizeof(数组名),数组名是整个数组,计算的是整个数组大小单位是字节;&arr取出的是整个数组的地址
void sort(int arr[],int sz)
{
int i = 0;
for(i = 0;i< sz - 1;i++)
{
int j ;
int flag = 1;
for(j = 0;j<i;j++)
{
if(arr[j]>arr[j+1])
{
int tmp = arr[j];
arr[j+1] = arr[j];
arr[j] = tmp;
flag = 0;
}
}
if(flag == 1)
{
break;
}
}
}
int main()
{
int arr[] = {0};
int sz = sizeof(arr) / sizeof(arr[0]);
sort(arr,sz);//数组传参传得是数组首元素地址 数组名本身就是首元素地址
for(int t = 0;t<sz;t++)
{
printf("%d ",arr[t]);
}
return 0;
}
十 操作符
一.算术操作符
+ - * / %
二.移位操作符
<<左移操作符(移动的是二进制位,电脑存储是32位整型,如果是2在电脑上显示则是30个0后10 若左移一位,则会变为29个0后为100,结果变为4)
>>右移操作符 包括逻辑右移和算数右移
逻辑右移 右边丢弃,左边为0
算数右移 右边丢弃,左边补原符号位
<<
>>
补充知识:
整数在内存中储存的是补码 一个整数的二进制表示有3种:原码反码和补码
举例来说 -1 对于负数才有以下计算原反补的关系,对于正整数而言,原反补码相同,不存在计算与转化
100000000000000000000001 (其中第一位是符号位,1代表负号)(原码)
111111111111111111111110 (符号位不变,其他位相对原码按位取反)(反码)
111111111111111111111111 (反码加一得到补码)(补码)
三.位操作符
& 按(二进制)位与
| 两个数字对应有1就1
^ 两个对应位数字不同为1,否则为1
注意 0^a=a a^a=0
int main()
{
int a = 3;
int b = 5;
int c = a&b;
//000000000000000000000011
//000000000000000000000101
int d = a|b;
int e = a^b;
printf("%d\n",c);//000000000000000000000001 1
printf("%d\n",d);//000000000000000000000111 7
printf("%d\n",e);//000000000000000000000110 6
return 0;
}
四.赋值操作符
= != += -= %= /= >>= ^= &=
注意:一个等号是赋值,两个等号是判断
五.单目操作符
!:!变量 逻辑反操作 非0变成0,0变成非0
+ -:正负值
sizeof:计算a所占空间的大小,单位是字节,一般 sizeof(),也可以不加(),这说明sizeof是操作符不是函数,其括号里的表达式不参与运算
~:按位取反 补码全反,1变0,0变1
//如a = -1内存补码 11111111111111111111111111111111
// ~a的结果为 00000000000000000000000000000000
++:前置++:先++ 再使用 后置++:先使用,后++
先使用的意思是先赋值,后使用的意思是后赋值给变量
//比如a = 1 b = ++a 这个时候 代码的输出结果会是2 2(a的值发生了改变)
// b = a++ 1 2
--:同++使用差不多
&:取地址操作符 //printf("%p\n",&a);
int * pa=&a;//*代表pa是指针变量
*:间接访问操作符(解引用操作符) 当需要通过地址找到指定变量赋值时 可以通过*来实现,比如*pa = 5;此时a的值就变成了5
(类型):如(unsigned int) 强制把变量转化为int类型
六.关系操作符
> < <= >= == !=
==:可以比较数字或字符是否相等,但是不能比较字符串是否相等
七.逻辑操作符
&& 逻辑与 同时满足为真 遇假停
|| 逻辑或 至少一个满足为真 遇真停
int main()
{
int i = 0,a = 0,b = 2,c = 3,d =4;
i = ++b && d++ && a++;
printf("a=%d b=%d c=%d d=%d",a,b,c,d);
return 0;
}
//结果为1335
int main()
{
int i = 0,a = 0,b = 2,c = 3,d =4;
i = a++ && ++b && d++;
printf("a=%d b=%d c=%d d=%d",a,b,c,d);
return 0;
}
//结果是1234
//原因是判断a为0时,i就已经确定是0,代码不会向下走
八.条件操作符
exp1?exp2:exp3
//表达式1 exp1的结果为真,则进入表达式2 exp2,若表达式1 exp1的结果为假,则进入表达式3 exp3
int main()
{
int a = 2;
int b = 3;
int max = 0;
/*if(a>b)
{
max = a;
}
else
{
max = b;
}
*/
//以上代码可以直接写为max = a>b a:b;
max = a>b? a:b;
printf("%d\n",max);
return 0 ;
}
九.逗号表达式
int main()
{
int a = 3;
int b = 5;
int c = 0;
//逗号表达式 要从左向右依次计算,但整个表达式的结果是最后一个表达式的结果
int d = (c = 5,a = c + 3,b = a - 4,c += 5);
printf("%d\n",d);
return 0;
}
十.下标引用、函数调用、结构体操作符
[]:比如arr[5] //下标引用操作符 有两个操作数 数组名arr 和 操作数5
():比如Add(x,y);//函数调用操作符 三个操作数 Add x y
.
->
//结构体
//结构体可以让C语言创建新的类型出来
//如创建一个学生
struct Stu
{
char name[20];
int age;//成员变量
double score;
};
int main ()
{
struct Stu s = {"zhangsan",20,90};//结构体的创建和初始化
printf("%s %d %f\n",s.name,s.age,s.score);//结构体变量.成员变量可以调出数值
//.操作符
struct Stu * ps = &s;
printf("2:%s %d %lf\n",(*ps).name,(*ps).age,(*ps).score);
printf("3:%s %d %lf",ps->name,ps->age,ps->score);
//结构体指针->成员变量名
return 0;
}
整型提升
当类型所占字节小于int时,会整型提升
int main()
{
char a = 3;
//00000000000000000000000000000011
//00000011 - a
char b = 127;
//00000000000000000000000001111111
//01111111 - b
char c = a + b;
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010
//100000010 - c
//11111111111111111111111110000010 - 部码
//11111111111111111111111110000001 - 反码
//10000000000000000000000001111110 - 原码
//-126
//a和b都是char类型,没有达到一个整型int的大小,会发生整型提升
printf("%d\n",c);
//c是%d为int类型,也需要进行整型提升
return 0;
}
//最终输出-126
算数转化
如果某个操作数的各个操作数属于不同的类型,那么除非一个操作数转化为另一个操作数的类型,否则操作无法运行,
long double
double
float
unsigned long int
long int
unsigned int
int
//寻常算数转化体系 向精度更高的转化
操作符的属性
复杂表达式求值三个影响因素
1.操作符的优先级 优先级决定了计算顺序
2.操作符的结合 优先级不起作用,结合性决定运算顺序(从左往右还是从右往左)
3.是否控制求值顺序
十一.指针
内存是电脑的存储器,计算机中所有程序的运行都是在内存中进行的,为了有效适用内存,内存被划分为一个个小的内存单元,每个内存单元的大小是一个字节。
为了有效访问到内存的每个单元,就给内存单元进行编号,这些编号被称为该内存单元的地址,此地址也被称作指针。
指针变量:地址如何存储,需要定义指针变量,指针变量的类型一般是由指向的对象类型加*组成。
int main()
{
int a = 10;//a在内存中需要分配空间 4个字节
printf("%p\n",&a);//%p是专门用来打印地址的
int * pa = &a;//pa是用来存放地址的,pa在C语言中是指针变量
// *说明pa是指针变量 int说明pa指向的对象是int类型
//以此类推,若b是字符char类型,存放地址的pc变量就应该在前面加chaar*
printf("%p\n",pa);
return 0;
}
*指针就是地址
int main()
{
int a = 10;//四个字节
int* pa = &a;//取地址拿到得是a的四个字节中第一个字节的地址
*pa = 30;
//这里的*是解引用操作符,单目操作符,*pa是通过pa里面的地址找到a,并给a赋值
//pa被称为指针变量
printf("%d\n",a);
return 0;
}
//指针的大小
int main()
{
printf("%d\n",sizeof(char*));
return 0;
}
指针的大小是相同的,指针是用来存储地址的,指针需要多大的空间,取决于地址的存储需要多大空间
32位平台 32bit-4byte 地址存放只需要四个字节
64位平台 64bit-8byte 地址存放需要八个字节>
指针和指针类型
a.指针类型决定指针解引用的权限多大
b.指针类型决定指针走一步的步长
#include <stdio.h>
int main()
{
int arr[]={0};
int *p = arr;
char *pa = arr;
printf("%p\n",p);
printf("%p\n",p+1);
printf("%p\n",pa);
printf("%p\n",pa+1);
return 0;
}
//结果:
//009ff714
//009ff718
//009ff714
//009ff715
//可以看出char* pa步长是1 int* p步长是4
野指针
a.野指针是指针指向目标不确定
int main()
{
//这里的p是野指针
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;//非法访问内存
return 0;
}
b.越界访问也会出现野指针问题
int main()
{
int arr[10] = {0};
int* p = arr;
int i;
for (i=0;i<=10;i++)
{
*p = i;
p++;
}
return 0;
}//下标到了10,有十一个元素
c.指针指向的内存已经释放也会导致野指针
*如何避免野指针
1.指针初始化 不知道初始化为什么的时候,直接初始化为NULL。
int* p = NULL;
2.小心指针越界。C语言本身不会检查数据越界行为,要自查。
3.指针指向空间释放及时置为NULL。
4.指针使用之前检查有效性。
指针运算
a.指针±整数
int main()
{
float values[5];
float *vp;
//指针关系运算
for (vp = &values[0];vp<&values[5];);
{
vp++;
}
return 0;
}
b.指针-指针得到的是指针的地址
指针与指针相减的前提是指向的是同一空间。
可以用于求字符串长度
int my_strlen(char* arr)
{
char* start = arr;
while(*arr != '\0')
{
arr++;
}
return arr - start;
}
int main()
{
char arr[] = {"abc"};
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的哪个内存位置的指针比较,但是不允许与指向第一个元素前的那个内存位置的指针进行比较。
指针和数组
数组名是首元素的首字节的地址
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
printf("%d\n",arr[2]);
printf("%d\n"),2[arr];
printf("%d\n",p[2]);
//[]是操作符,2和arr是操作数,满足交换律
//即arr[2]<-->*(arr+2)<-->*(2+arr)<-->2[arr]
//同理p[2]-->*(p+2)
return 0;
}
二级指针
int main()
{
int a = 0;
int* p = &a;
int** pa = &p;//第二个*代表指针指向p,pa是指针变量,第一个*表示指针指向的对象p是int*类型
//pa是二级指针变量,解引用到a;
**pa=29;
printf("%d\n",a);
return 0;
}
以此类推,三级指针int***
指针数组
int arr[10];//整型数组 存放整型的数组
char ch[4];//字符数组 存放字符的数组
int * pa[5];//整型指针数组 存放整型字符的数组
十二.结构体
基本概念
结构是一些值的结合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量
结构成员的类型
标量、结构体、数组,指针
结构体变量的定义和初始化
struct Ponit
{
int x,y;
}p1;//声明类型的同时定义变量p1
struct Point p2;//定义结构体变量p2
//初始化,定义变量同时赋值
struct Point p3 = {x,y};
结构体成员访问
用.或者->访问
具体方法:
//结构体
//结构体可以让C语言创建新的类型出来
//如创建一个学生
struct Stu
{
char name[20];
int age;//成员变量
double score;
};
int main ()
{
struct Stu s = {"zhangsan",20,90};//结构体的创建和初始化
printf("%s %d %f\n",s.name,s.age,s.score);//结构体变量.成员变量可以调出数值
//.操作符
struct Stu * ps = &s;
printf("2:%s %d %lf\n",(*ps).name,(*ps).age,(*ps).score);
printf("3:%s %d %lf",ps->name,ps->age,ps->score);
//结构体指针->成员变量名
return 0;
}
结构体传参
如果写一个打印内容的函数,最好用传址调用,这样可以省空间,且时间耗费少,并且传址调用可以改变主函数的值。
压栈
3.指针指向空间释放及时置为NULL。
4.指针使用之前检查有效性。
指针运算
a.指针±整数
int main()
{
float values[5];
float *vp;
//指针关系运算
for (vp = &values[0];vp<&values[5];);
{
vp++;
}
return 0;
}
b.指针-指针得到的是指针的地址
指针与指针相减的前提是指向的是同一空间。
可以用于求字符串长度
int my_strlen(char* arr)
{
char* start = arr;
while(*arr != '\0')
{
arr++;
}
return arr - start;
}
int main()
{
char arr[] = {"abc"};
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的哪个内存位置的指针比较,但是不允许与指向第一个元素前的那个内存位置的指针进行比较。
指针和数组
数组名是首元素的首字节的地址
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
printf("%d\n",arr[2]);
printf("%d\n"),2[arr];
printf("%d\n",p[2]);
//[]是操作符,2和arr是操作数,满足交换律
//即arr[2]<-->*(arr+2)<-->*(2+arr)<-->2[arr]
//同理p[2]-->*(p+2)
return 0;
}
二级指针
int main()
{
int a = 0;
int* p = &a;
int** pa = &p;//第二个*代表指针指向p,pa是指针变量,第一个*表示指针指向的对象p是int*类型
//pa是二级指针变量,解引用到a;
**pa=29;
printf("%d\n",a);
return 0;
}
以此类推,三级指针int***
指针数组
int arr[10];//整型数组 存放整型的数组
char ch[4];//字符数组 存放字符的数组
int * pa[5];//整型指针数组 存放整型字符的数组
十二.结构体
基本概念
结构是一些值的结合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量
结构成员的类型
标量、结构体、数组,指针
结构体变量的定义和初始化
struct Ponit
{
int x,y;
}p1;//声明类型的同时定义变量p1
struct Point p2;//定义结构体变量p2
//初始化,定义变量同时赋值
struct Point p3 = {x,y};
结构体成员访问
用.或者->访问
具体方法:
//结构体
//结构体可以让C语言创建新的类型出来
//如创建一个学生
struct Stu
{
char name[20];
int age;//成员变量
double score;
};
int main ()
{
struct Stu s = {"zhangsan",20,90};//结构体的创建和初始化
printf("%s %d %f\n",s.name,s.age,s.score);//结构体变量.成员变量可以调出数值
//.操作符
struct Stu * ps = &s;
printf("2:%s %d %lf\n",(*ps).name,(*ps).age,(*ps).score);
printf("3:%s %d %lf",ps->name,ps->age,ps->score);
//结构体指针->成员变量名
return 0;
}
结构体传参
如果写一个打印内容的函数,最好用传址调用,这样可以省空间,且时间耗费少,并且传址调用可以改变主函数的值。
压栈
参数传参也被叫做压栈操作。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,性能会下降。