函数的一些基本知识
1.函数定义格式:
返回值类型 函数名 (参数列表)
{
代码体
return x;(x的类型为返回值类型)
}
例如:
int add(int a,int b) (void类型的话可以不要return)
{
int sum=a+b;
return sum;
}
函数分类:库函数;系统自带的函数
定义函数:用户自定义的函数
2.函数的调用:
函数的调用过程中参数传递方向是将实参传给形参
在不同函数中的函数里的变量名可以重名,因为变量的作用域不同
(拓展:局部变量:作用域是一个函数,各个局部变量名可以重名
全局变量;作用域是整个程序,变量名不能重复
局部变量名跟全局变量名不能重名,作用域会冲突)
形式参数:函数定义中的参数,没有具体的值,也称为形参,
实际参数; 函数调用过程传递的参数,有具体的值,也称为实参
函数调用时,编译系统会给形参分配内存单元;
调用结束后,返回值返回到存储器中,其他函数信息会被销毁,形参内存单元被释放
具体例子如图
eg:
无参函数:参数列表里为空
void wucan() //参数列表没有参数,函数参数类型为空
{}
(无参无返函数:void wucanfan(void)
{} )
有参函数:参数列表里参数不为空
eg:
int add(int a,int b) //(void类型的话可以不要return)
{
int sum=a+b;
return sum;
}
3.函数使用流程:
函数声明(如果函数定义放在函数调用之前的话可以不用声明)
eg:
(extern)int add(int a,int b); //extern可以不用
函数定义
函数调用
(拓展:声明变量不占用存储空间
定义变量占用存储空间
声明包含定义,定义是声明的一个特例)
拓展:exit()函数:可以用来抛出异常时终止程序执行
4.多文件编程:
也叫模块化编程。把不同功能的函数封装到不同的文件中。一个.c文件和一个.h文件被称为一个模块。
头文件:.h文件
(里面存放全局变量和函数声明)
源文件:.c文件
(调用.h文件)
一般来说,我们需要在头文件里面避免同一个函数被多次包含引起错误。方法有两种如下:
①.
#ifndef _HEAD_H_
#define _HEAD_H_
声明语句
#endif
②.
#pragma once
声明语句
一般使用的比较多的是第一种 。第一种可以在linux中使用,而第二种只能在windows中的VS或者QT…使用。
具体流程如下:
指针的一些骚操作
1.概述
内存:即存储器,在计算机组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分
内存:内部存储器,暂存程序/数据,数据掉电丢失 比如:SRAM,DDR等
外存:外部存储器,长时间保存数据/程序,数据掉电不丢失 比如:ROM,FLASH,硬盘,光盘等
(对比单片机中 RAM(数据存储器):存储变量,运算过程的中间结果,标志位等,掉电数据丢失,
ROM(只读存储器):存储程序,原始数据或表格,掉电数据不丢失)
指针定义:在内存中,地址指向变量单元,地址可以形象的比作指针。通过指针能找到以它为地址的内存单元。总的来说指针就是内存地址,内存地址就是指针
*:取值运算符,降维度
&:取地址运算符,升维度
int a=10; //直接给a赋值
int* p=&a; //把a的地址值赋给整型指针变量P()
*p=100 //改变p对应的地址里面的内容,间接改变a的值,此时a的输出值是100
int p=&a; //定义p是整型变量
*(int*)p=100; //(int*)是强制类型转换,将整型变量P转换为整型指针变量
在定义指针的类型一定要和变量类型对应上,否则打印出来的数据会出错
例如:
void main()
{
char ch='a'; //相当于97
int* p=&a;
//*p=1000; //直接修改指针变量对应内存单元的值会报错
printf("%d\n",ch); //打印结果是97
printf("%d\n",*p); //打印结果是-858993567
}
所有的指针类型都是存储的都是内存地址,内存地址都是一个无符号十六进制整型数
特殊说明:在32位操作系统下所有指针类型都是4个字节大小
在64位操作系统下所有指针类型都是8个字节大小
2.特殊指针:
① 野指针:指针变量(地址)指向一个未知的空间(地址的性质未知),也就是说直接给指针变量随意给定一个值(地址),这个地址是未知的。
程序中允许野指针存在,但是不建议直接将一个变量的值或者一个固定的值赋给指针。由于操作系统将0~255作为系统占用不允许访问操作,所以访问野指针对应的内存空间可能会报错
例如:
void main()
{
int* p=100; //指针变量被直接赋值,所以p是野指针
printf("%d\n",*p);
}
②空指针:空指针是指内存地址编号位0的地址(常用作判断用)
所以访问空指针对应的内存空间一定会报错
例如:
void main()
{
int* p=NULL; //指针变量为空也就是0,所以p是空指针
printf("%d\n",*p);
}
③万能指针:可以接受任何类型的变量的内存地址,在通过
万能指针修改变量的值时,要找到变量对应的指针类型
例如:
void main()
{
int a=10;
void* p=&a;
printf("万能指针在内存中占用的字节大小:%d\n",sizeof(void*)); //结果是4
*(int*)p=100;
printf("%d\n",*(int*)p); //结果是100,如果没有强制转换(int*)会报错
}
④const修饰的指针
const修饰的变量是不允许被直接改变的,但是可以通过指针间接改变
例如1:const修饰变量
void main()
{
const int a=10;
//a=100; //写这句直接修改a的值会报错
int* p=&a;
*p=100;
printf("%d\n",*p); //通过指针变量间接改变a的值,输出是100
}
例如2:const修饰指针类型(const int* p=&a)
可以修改指针变量的值,不可以修改指针变量对应的内存单元的值
void main()
{
int a=10;
int b=20;
const int*p=&a;
p=&b; //可以执行
//*p=100; //会报错
printf("%d",*p);
}
例如3:const修饰指针变量( int* const p=&a)
不可以修改指针变量的值,可以修改指针变量对应的内存单元的值
void main()
{
int a=10;
int b=20;
int* const p=&a;
//p=&b; //会报错
*p=100; //可以执行
printf("%d",*p);
}
从2和3可以知道,对于指针,const修饰离他最近的那个变量或类型
例如4:const修饰一级指针变量和指针类型(const int* const p=&a)
不可以修改一级指针变量的值,也不可以修改一级指针变量对应的内存单元的值
void main()
{
int a=0;
int b=0;
const int* const p=&a;
p=&a; //会报错
*p=100; //会报错
}
但是 ,对于多级指针,即使const修饰一级指针变量和指针类型,也能通过多级指针来间接修改一级指针变量及其对应的内存单元的值,例子如下:
void mian()
{
int a=10;
int b=20;
const int* const p=&a;;
int** p=&p;
*p=&b
printf("%d\n",*p); //输出是20
**p=100;
printf("%d\n";*p); //输出是100
}
3.指针和数组
数组是一个常量,不允许赋值
数组名的值是数组首元素地址
注意:数组作为函数参数会退化为指针,丢失数组精度(即丢失数组大小)
例如:
void main()
{
int arr[]={1,2,3,4,5,6,'a'};
//arr=100; //会报错
int* p;
p=arr;
//指针和数组的区别
//p是变量,arr是常量
//p是一个4个字节的指针,arr是一个40个字节的地址常量
//指针加1,相当于指向数组下一个元素
printf("%d\n",p); //打印出来的值和下面的一样
printf("%d\n",arr); //打印出来的值和上面的一样
for(i=0;i<7;i++)
{
printf("%d\n",arr[i]);
printf("%d\n",p[i]);
printf("%d\n",*(arr+i)); //三种打印方法打印结果相同 *(arr+2)-->arr[2]
}
}
指针的加减和指针类型有关
指针类型变量加1,内存地址偏差是4,指针偏移量=内存地址偏差/指针类型所占字节(sizeof(int))
比如:p+=1;则地址从0xff00–>0xff04
4.指针数组:数组的元素类型是指针类型,是一个特殊的二维数组模型
例如1:数组里面是常量元素地址
void main()
{
int a=1;
int b=2;
int c=3;
int* arr[3]={&a,&b,&c}; //数组里面的元素全是地址
for(i=0;i<3;i++)
{
printf("%d/t",*arr[i]);
}
}
例如2:数组里面是数组元素地址(相当于一个二维数组)
void main()
{
int a[]={1,2,3};
int b[]={4,5,6};
int c[]={7,8,9};
int* arr[]={a,b,c};
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
printf("%d ",*arr[i][j]);
printf("%d ",*(arr[i]+j));
printf("%d ",*(*(arr+i)+j)); //三种打印方法都可以实现
}
puts(" ");
}
}
5.多级指针:也就是指针的指针的指针……。
一级指针用的比较多,其次是二级,三级指针及以上用的很少
示例代码:
void main()
{
int a=10;
int b=20;
int* p=&a;
int* *pp=&p; //二级指针变量对应的是一级指针变量的地址
int** *ppp=&pp; //三级指针变量对应的是二级指针变量的地址
//*ppp==pp=&p
//**ppp==*pp==p=&a
//***ppp==**pp==*p==a
*pp=&b //取出二级指针变量对应对应一级指针变量p,再把b的地址赋给一级指针变量p。等价于p=&b
**pp=100;
//*pp=100;//err
printf("%d\n",*p);
printf("%d\n",a);
}
6.指针函数:指针作为函数参数
示例:
void swap(int* a,int* b)
{
int temp=*a;
*a=*b;
*b=temp;
}
void main()
{
int a=10;
int b=20;
swap(&a,&b); //地址传递 形参可以改变实参的值,若为值传递,则形参不可以改变实参的值
printf("%d\n",a);
printf("%d\n",b);
}
7.指针数组名作为函数参数
示例:
void mystracat(char* ch1,char* ch2)
{
while(*ch1)ch1++;
while(*ch2)
{
*ch1=*ch2;
ch1++;
ch2++;
}
//while(*ch1++=*ch2++)相当于
//if(*ch1++==*ch2++)
//{ch1++;ch2++;}
}
8.指针作为函数返回值
示例:
char* my_atrchr(char* str,char ch) //注意函数的定义是指针类型,固返回值应该是一个地址
{
int i=0;
while(str[i])
{
if(str[i]==ch)
{
return &str[i];
}
i++;
}
return NULL;
}
9.指针和字符串
void main()
{
char ch[]="hello world"; //栈区字符串可以修改
char* p="hello world"; // 数据区常量区字符串不可以修改
ch[2]='m';
printf("%s",ch); //可以修改,修改后的值是hemlo world
//p[2]='m';
//*(p+2)='m';//指针的修改不了
}
10.字符指针作为函数参数
示例:
int my_strlen(char* ch)
{
//计算字符串的有效长度
int i=0;
while(ch[i]!='\0')
{
i++;
}
return i;
}
或者:
int my_strlen(char* ch)
{
//计算字符串长度
char* temp=ch;
while(*temp)temp++;
return temp=ch
}
总结:
如图
字符串
文章转载至
http://blog.bools.cn/archives/167