const关键字、函数
一、const关键字
const修饰变量的时候,表示不能通过变量名,修改变量的值。
const int a = 10;
a = 20; //错误的 const 修饰的是一个只读变量 不能通过变量名修改
const 修饰指针的时候:
const int *p;
int const *p;
int * const p;
const int * const p;
//区别的时候 注意 * 和const 的相对位置关系
//如果const在 *的左边表示 修饰的是 *p
//不能通过 p 修改指向的空间的内容
//但是 p 的指向是允许修改的
//如果const在 *的右边表示 修饰的是 p
//指针的指向不能修改
//但是允许通过指针修改指向的空间里的内容
//如果*的两边都有const,
//表示指针的指向不能修改,
//也不能通过指针修改指向空间里的内容
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
#if 0
int a = 100;
int b = 200;
const int *p = &a;
//*p = 1234;//错误的
a = 1234; //通过变量自身是可以修改的
p = &b;//正确的
#endif
#if 0
//和上面的写法是等价的 只不过一般用上面的写法比较多
int a = 100;
int b = 200;
int const *p = &a;
//*p = 1234;//错误的
p = &b;//正确的
#endif
#if 0
int a = 100;
int b = 200;
int * const p = &a;
*p = 1234;//正确的
//p = &b; //错误的
#endif
int a = 100;
int b = 200;
const int * const p = &a;
//*p = 1234;//错误的
//p = &b; //错误的
return 0;
}
二、函数
2.1 概念
将实现某些功能的代码,封装成代码块,每次想使用这个功能的时候
无需编写重复的代码,只需要通过代码块的名字调用即可。
这个代码块就叫做函数,代码块的名字就叫做函数名。
如:printf strcpy strcat atoi 等 都是函数
2.2 函数的定义和调用
定义函数的格式:
返回值类型 函数名(函数的参数列表){
函数体;//也就是我们要实现功能的代码块
}
函数名 是一个标识符,要符合标识符的命名规范。
例:
#include <stdio.h>
//int 是函数的返回值类型 如果有返回值就写类型
// 如果没有返回值 可以写成 void 但是注意不能不写
//print_menu 是函数名 是我们自己起的名字
//() 里面是函数的参数列表 如果有参数就写 没有参数可以写void 或者直接空着不写
//{} 里面扩住的代码就是函数体 就是实现功能的代码块
//return 是用来返回函数运行的结果的 如果有返回值 就写
// 要注意返回值的类型要和函数名前面的类型保持一致
// 如果没有返回值 可以不写return 或者 直接写 return;
int print_menu(void){
printf("--------------------------\n");
printf("| 1.注册 2.登陆 3.退出 |\n");
printf("--------------------------\n");
printf("please input your choose : ");
return 0;
}
//函数一旦定义好之后 就可以在其他函数中调用了
//函数中的代码如果没有被调用 是不会执行的
void my_test(){
printf("my_test start\n");
print_menu();
printf("my_test end\n");
}
int main(int argc, const char *argv[])
{
//通过函数名即可调用函数
// () 里面是要给函数传的参数 如果没有参数 可以不写 但是 () 必须写
//程序执行到调用函数的位置 就会跳转到函数内部执行代码
// 执行完在跳转会函数调用处 继续向下执行
print_menu();
int choose = 0;
scanf("%d", &choose);
printf("choose = %d\n", choose);
my_test();
return 0;
}
2.3 函数的声明
函数定义好之后,可以在其他函数中掉用,如果我们把所有函数都定义在main函数的上面
那么在main函数中调用是没有问题的,但是函数之间相互调用,就有可能出现不认识的情况
这时就需要用到函数的声明了。
#include <stdio.h>
//函数的声明的格式
void func1();
void func2();
void func3();
int main(int argc, const char *argv[])
{
func1();
return 0;
}
//函数的定义
void func1(){
printf("i am func1..\n");
func2();
}
//函数的定义
void func2(){
printf("i am func2..\n");
func3();
}
//函数的定义
void func3(){
printf("i am func3..\n");
}
2.4 函数的参数
函数为什么要有参数?
在函数实现功能的过程中,有些值,函数里面没有,就需要调用者
在调用函数的时候,通过参数给函数传递过来。
例:
#include <stdio.h>
//有参数的函数声明
//void my_add(int x, int y);
void my_add(int, int);//也可以只写形参的类型 不写形参的名字
int main(int argc, const char *argv[])
{
int a = 100;
int b = 200;
//有参数的函数调用
//调用函数时传递的参数叫做实际参数 简称--实参
//调用函数时 实参的类型和个数 必须和形参保持一致
my_add(a, b);
my_add(10, 20);
return 0;
}
//定义函数时,函数的()里面的叫做函数的形式参数 简称--形参
//形参相当于对函数参数格式的约定 约定了调用者需要传 几个及什么类型的参数
//在调用函数的过程中 操作系统会给函数的形参分配空间 然后用实参来初始化形参
//注意 在函数内部 对形参如何修改都不会影响实参 因为 他们在不同的内存空间上
void my_add(int x, int y){
int temp = x+y;
printf("%d+%d = %d\n", x, y, x+y);
}
//形参占用的内存空间 在函数调用结束时 会被操作系统回收
2.5 函数的返回值
函数为什么要有返回值?
有些时候,函数运行的结果需要返回给调用处,供后面使用,
这时候就需要用到返回值了。需要返回值就写,不需要可以不写。
#include <stdio.h>
int my_add(int x, int y){
int temp = x+y;
//return 后面可以是变量 也可以是常量 也可以是表达式
//只要保证和 函数名前面的类型一致即可
return temp; //遇到return 就立即返回 后面的代码都不执行了
printf("hello world\n");//不执行
}
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
int ret = 0;//定义一个变量 用来接收函数的返回值
ret = my_add(a, b);
printf("ret = %d\n", ret);
//即使函数有返回值 也可以不接 具体业务逻辑
my_add(100, 200);
return 0;
}
实际开发的过程中,定义的函数一般都是有返回值的,
因为可以通过返回值来判断函数的执行情况,从而决定如何处理
如下面的伪代码:
#define ERR_NET -1
#define ERR_DATABASE -2
#define ERR_LOG -3
#define ERR_USER -4
#define SUCCESE 0
int pro_init(){
if(连接网络失败){
return ERR_NET;
}
if(连接数据库失败){
return ERR_DATABASE;
}
if(加载日志文件失败){
return ERR_LOG;
}
if(加载用户信息失败){
return ERR_USER;
}
return SUCCESE;
}
int main(){
int ret = pro_init();
if(SUCCESE != ret){
//可以根据 ret 的不同走不同的处理逻辑
}
return 0;
}
练习:
1.封装一个 [m,n] 求和的函数,m和n由参数传递
通过返回值返回求和的结果,调用并测试
#include <stdio.h>
int my_sum(int m, int n){
int i = 0;
int sum = 0;
for(i = m; i <= n; i++){
sum+=i;
}
return sum;
}
int main(int argc, const char *argv[])
{
int ret = my_sum(1, 10);
printf("ret = %d\n", ret);//55
printf("%d\n", my_sum(1,100));//5050
return 0;
}
2.编写4个函数 分别实现两个整数的加减乘除的功能,返回值返回计算的结果
my_add
my_sub
my_mul
my_div
#include <stdio.h>
int my_add(int x, int y){
return x+y;
}
int my_sub(int x, int y){
return x-y;
}
int my_mul(int x, int y){
return x*y;
}
double my_div(int x, int y){
return (double)x/(double)y;
}
int main(int argc, const char *argv[])
{
printf("%d\n", my_add(7, 4));
printf("%d\n", my_sub(7, 4));
printf("%d\n", my_mul(7, 4));
printf("%lf\n", my_div(7, 4));
return 0;
}
2.6 全局和局部
#include <stdio.h>
//生命周期:占用的内存空间何时被回收
//作用域:在哪个范围内能访问
//全局变量:没有被任何{}包住的变量
int value2 = 1314;
//生命周期:整个程序结束
//作用域:整个文件
void my_test(){
int value3 = 520;//局部
//printf("value1 = %d\n", value1);//访问不了
printf("value2 = %d\n", value2);//1314
printf("value3 = %d\n", value3);//520
}
int main(int argc, const char *argv[])
{
//局部变量:被{}包住的变量
int value1 = 10;
//生命周期:最近的{}结束
//作用域:最近的{}
printf("value1 = %d\n", value1);//10
printf("value2 = %d\n", value2);//1314
//printf("value3 = %d\n", value3);//访问不了
my_test();
return 0;
}
2.7 函数的传参方式
2.7.1 全局传参
----了解即可 基本不使用
#include <stdio.h>
int ret = 0;
int lvalue = 0;
int rvalue = 0;
void my_add(){
ret = lvalue+rvalue;
}
int main(int argc, const char *argv[])
{
lvalue = 10;
rvalue = 20;
my_add();
printf("ret = %d\n", ret);//30
return 0;
}
2.7.2 复制传参(值传递)
形参只是将实参的值保存了一份儿
在函数内部无论怎样修改形参,都不会影响实参,因为他们不在同一块内存空间上。
#include <stdio.h>
//功能:将参数放大10倍 再求和
int my_add_ten(int x, int y){
printf("func : &x = %p, &y = %p\n", &x, &y);//和下面main函数中xy的地址不一样
x = x * 10;
y = y * 10;
int temp = x+y;
printf("func : x = %d, y = %d\n", x, y);//100 200
return temp;
}
int main(int argc, const char *argv[])
{
int x = 10;
int y = 20;
printf("main : &x = %p, &y = %p\n", &x, &y);//和上面子函数中xy的地址不一样
printf("main 前 : x = %d, y = %d\n", x, y);//10 20
int ret = my_add_ten(x, y);
printf("ret = %d\n", ret); //300
printf("main 后 : x = %d, y = %d\n", x, y);//10 20
return 0;
}
2.7.3 地址传参(地址传递)
如果想在函数中修改实参的值,就需要将实参的地址传给函数。
#include <stdio.h>
//此处 x 和 y 是值传递 z是地址传递
void my_add1(int x, int y, int *z){
printf("my_add1 : z = %p\n", z);
*z = x+y;
printf("my_add1 : z = %d\n", *z);//30
}
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
int ret1 = 0;
printf("main : &ret1 = %p\n", &ret1);
my_add1(a, b, &ret1);
printf("ret1 = %d\n", ret1); //30
return 0;
}
练习:
定义一个函数,实现两个整数的交换。
#include <stdio.h>
void my_swap(int *p, int *q){
int temp = *p;
*p = *q;
*q = temp;
}
int main(int argc, const char *argv[])
{
int x = 10;
int y = 20;
my_swap(&x, &y);
printf("x = %d y = %d\n", x, y);//20 10
return 0;
}
//形参是指针,也不一定是地址传递,有可能是指针的值传递。
#include <stdio.h>
int m = 10;
int n = 20;
#if 0
//功能:改变指针指向
void my_chage1(int *q){
q = &n;
}
#endif
void my_chage2(int **q){
*q = &n;
}
int main(int argc, const char *argv[])
{
#if 0
int *p = &m;
printf("*p = %d\n", *p);//10
my_chage1(p);
printf("*p = %d\n", *p);//10
#endif
int *p = &m;
printf("*p = %d\n", *p);//10
my_chage2(&p);
printf("*p = %d\n", *p);//20
printf("p = %p &n = %p\n", p, &n);//一样的
return 0;
}
2.8 数组的传参方式
2.8.1 字符串的传参方式
只传首地址即可,因为字符串有 ‘\0’ 来作为结束的标志。
#include <stdio.h>
char *my_strcpy(char *dest, const char *src){
char *temp = dest;//备份目标字符串的首地址 用作返回值
while(*src != '\0'){
*dest++ = *src++;
}
*dest = *src;
return temp;
}
int main(int argc, const char *argv[])
{
char s1[32] = "hello world";
char s2[32] = "beijing";
char *p = my_strcpy(s1, s2);
printf("s1 = %s\n", s1);
printf("s2 = %s\n", s2);
printf("p = %s\n", p);
return 0;
}
2.8.2 整型数组的传参方式
既需要传首地址,也需要传数组的长度,
因为 整型数组是没有结束标志的。
#include <stdio.h>
//功能 遍历一维数组
void print_array(int *p, int len){//常用的写法
int i = 0;
for(i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
}
//数组传参使用下面的写法也可以
//下面这两种写法叫做 代码的自注释
//这种写法的本质 p 也是指针
void print_array2(int p[100], int len){
//void print_array2(int p[], int len){
printf("sizeof(p) = %ld\n", sizeof(p)); // 8 说明p是指针
int i = 0;
for(i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
}
int main(int argc, const char *argv[])
{
int s1[5] = {1,2,3,4,5};
print_array(s1, 5);
int s2[10] = {1,2,3,4,5,6,7,8,9,10};
print_array(s2, 10);
print_array2(s2, 10);
return 0;
}
练习:
封装一个函数,功能是查找一维数组中的最大元素,
通过返回值返回最大元素。
定义一个长度为10的数组,调用测试。
#include <stdio.h>
int find_max(int *p, int len){
int max_index = 0;
int i = 0;
for(i = 1; i < len; i++){
if(p[max_index] < p[i]){
max_index = i;
}
}
return p[max_index];
}
int main(int argc, const char *argv[])
{
int s[8] = {11,22,33,44,34,666,78,9};
int ret = find_max(s, 8);
printf("ret = %d\n", ret);
return 0;
}
2.9 二维数组的传参方式
需要使用 数组指针 最为函数的形参。
例:
#include <stdio.h>
//遍历二维数组
void print_array(int (*p)[4], int hang, int lie){
int i = 0;
int j = 0;
for(i = 0; i < hang; i++){
for(j = 0; j < lie; j++){
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main(int argc, const char *argv[])
{
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
print_array(s, 3, 4);
return 0;
}
2.10 main函数的参数
#include <stdio.h>
int main(int argc, const char *argv[])
{
//argc 是执行程序时命令行参数的个数(包括可执行文件名的)
printf("argc = %d\n", argc);
int i = 0;
for(i = 0; i < argc; i++){
printf("argv[%d] = %s\n", i, argv[i]);
}
//argv[0] 就是指向可执行文件名的指针
printf("argv[0] = %s\n", argv[0]);
return 0;
}
思考:将 const char *argv[] 改成 const char **argv 可不可以?
答案:可以 因为操作空间都是 一个指针的大小。
1.将前面讲的冒泡排序的逻辑封装成函数
要求,多设置一个变量 flag, 别人调用时
给 flag 传 0 升序
给 flag 传 1 降序
#include <stdio.h>
int my_swap(int *p, int x, int y){
int temp = p[x];
p[x] = p[y];
p[y] = temp;
}
int my_sort(int *p, int len, int flag){ //flag 0 升序 1 降序
if(NULL == p){//防止传参传的是空指针的 检查
return -1;
}
int i = 0;
int j = 0;
if(0 == flag){//升序
for(i = 0; i < len-1; i++){
for(j = 0; j < len-i-1; j++){
if(p[j] > p[j+1]){
my_swap(p, j, j+1);
}
}
}
}else if(1 == flag){//降序
for(i = 0; i < len-1; i++){
for(j = 0; j < len-i-1; j++){
if(*(p+j) < *(p+j+1)){
my_swap(p, j, j+1);
}
}
}
}
return 0;
}
void print_array(int *p, int len){
int i = 0;
for(i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
}
int main(int argc, const char *argv[])
{
int s[10] = {11,22,33,66,44,55,88,77,99,10};
print_array(s, 10);
//升序
my_sort(s, 10, 0);
print_array(s, 10);
//降序
my_sort(s, 10, 1);
print_array(s, 10);
return 0;
}
2.实现一个简易的计算器功能(能实现加减就行),要求命令行传参
如: ./a.out 10 + 20 -->>30
./a.out 5 - 4 —>>1
#include <stdio.h>
int my_swap(int *p, int x, int y){
int temp = p[x];
p[x] = p[y];
p[y] = temp;
}
int my_sort(int *p, int len, int flag){ //flag 0 升序 1 降序
if(NULL == p){//防止传参传的是空指针的 检查
return -1;
}
int i = 0;
int j = 0;
if(0 == flag){//升序
for(i = 0; i < len-1; i++){
for(j = 0; j < len-i-1; j++){
if(p[j] > p[j+1]){
my_swap(p, j, j+1);
}
}
}
}else if(1 == flag){//降序
for(i = 0; i < len-1; i++){
for(j = 0; j < len-i-1; j++){
if(*(p+j) < *(p+j+1)){
my_swap(p, j, j+1);
}
}
}
}
return 0;
}
void print_array(int *p, int len){
int i = 0;
for(i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
}
int main(int argc, const char *argv[])
{
int s[10] = {11,22,33,66,44,55,88,77,99,10};
print_array(s, 10);
//升序
my_sort(s, 10, 0);
print_array(s, 10);
//降序
my_sort(s, 10, 1);
print_array(s, 10);
return 0;
}
2.实现一个简易的计算器功能(能实现加减就行),要求命令行传参
如: ./a.out 10 + 20 -->>30
./a.out 5 - 4 —>>1
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
//对命令行参数的个数 做检查
if(4 != argc){
printf("Usage : %s lvalue opretor rvalue\n", argv[0]);
return -1;
}
int lvalue = atoi(argv[1]);
int rvalue = atoi(argv[3]);
char opretor = *argv[2];
switch(opretor){
case '+':
printf("%d\n", lvalue + rvalue);
break;
case '-':
printf("%d\n", lvalue - rvalue);
break;
default:
printf("目前只支持加减运算..\n");
break;
}
return 0;
}