C基础-入门
不得不说学习C/C++将是一个噩梦般的过程,不过学习带来的成果也是不可估量的,下面我们使用jetbrins的clion开发工具,重学C/C++。
环境配置
CLion的安装这里就不多说了,这里提一下,mac如果安装了XCode就不需要配置什么了,如果是Windows系统,需要安装Cygwin,在下面红框处配置Cygwin环境即可。
Hello World
新建项目
环境配置完成后,新建一个项目
可以看到,左侧菜单栏有很多选项
- C++ Execuable:创建一个C++项目
- C++ Library:创建一个C++依赖库项目
- C Execuable:创建一个C项目
- C Library:创建一个C依赖库项目
这里我们从C语言开始学起,创建一个C Execuable,标准库选择默认的C99即可。可以看到,创建完成后,编辑器为我们创建了两个文件
CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(untitled1 C)
set(CMAKE_C_STANDARD 99)
add_executable(untitled1 main.c)
其中CMakeLists.txt
是一个配置文件,作用相当于androidstudio的settings.gradle,add_executable
中可以有多个参数,参数之间以空格分开,第一个参数就是项目目录,后面的参数就是c文件了。
main.c
#include <stdio.h>
int main() {
printf("Hello, World!你好\n");
return 0;
}
第一行#include
,导入头文件,相当于android的import
关键字,后面以<>符号括起来的包代表系统包,以""符号括起来的包代表我们自己定义的包。
stdio.h
头文件包含了printf方法,所以下面才能够调用。
这时项目就可以运行起来了
中文乱码
输出的中文文字可能会有乱码的问题
这里有个奇怪的问题:
-
右下角有个UTF-8编码格式,如果是windows系统,默认的是UTF-8,需要改成GBK编码
-
如果是macOS,默认的就是GBK,需要转成UTF-8
这时会弹出一个对话框,点击convert,再次运行即可。
函数
在c语言中,执行顺序是死板的,如果我们调用的函数在当前函数下面,编译器是会报错的,比如:
这是把test函数放到main函数上面即可。
函数声明
如果非要在下面定义函数,那么就需要在最上面进行函数声明:
void test();
int main() {
printf("main\n");
test();
return 0;
}
void test() {
printf("test\n");
}
这样也是可以的
占位符
如果需要打印某个变量,跟java不同的是,c中需要使用占位符。比如:
int i = 100;
printf(i);
这样系统将会报错
我这里没有报错信息,但是可以看到,code为139,非正常退出。那应该怎么办呢?其实跟java中的占位符是一样的
int i = 100;
printf("%d\n", i);
如果是int类型,那么使用%d
进行占位,这里我进行了简单的整理,每个数据类型都需要哪些占位符呢
类型 | 占位符 |
---|---|
int or short | %d |
double | %lf |
float | %f |
long | %ld |
char | %c |
char*(字符串) | %s |
指针 | %p |
测试一下:
int main() {
printf("Hello, World!你好\n");
int i = 100;
printf("%d\n", i);
double d = 10;
printf("%lf\n", d);
float f = 100;
printf("%f\n", f);
long l = 100;
printf("%ld\n", l);
short sd = 1;
printf("%d\n", sd);
char c = 'a';
printf("%c\n", c);
char *str = "kf";
printf("%s\n", str);
// getchar(); // 阻塞程序,不停止运行
return 0;
}
打印
数据类型
新建一个c文件,测试一下数据类型所占字节,注意,一个项目里面只能有一个main入口,新建文件如果写了main函数,那么需要将其他文件的main函数注释掉
#include <stdio.h>
//
// Created by hp on 2021/2/23.
// sizeof 查看类型占用字节数
//
int main() {
printf("int 占用字节: %lu", sizeof(int));
printf("double 占用字节: %lu\n", sizeof(double));
printf("char 占用字节: %lu\n", sizeof(char));
printf("short 占用字节: %lu\n", sizeof(short));
printf("long 占用字节: %lu\n", sizeof(long));
return 0;
}
使用sizeof函数可以查看某个类型所占用的字节数
/Users/kangfan/work/Cwork/study_1/cmake-build-debug/study_1
int 占用字节: 4
double 占用字节: 8
float 占用字节: 4
char 占用字节: 1
short 占用字节: 2
long 占用字节: 8
Process finished with exit code 0
对象的地址
在c中,可以在变量前面使用符号&,获取该对象的地址信息:
int main() {
int num1 = 1090;
printf("num1的地址是 :%p\n", &num1);
return 0;
}
可以看到打印
/Users/kangfan/work/Cwork/study_1/cmake-build-debug/study_1
num1的地址是 :0x7ffee50be768
Process finished with exit code 0
指针
在c中,可以说万物皆指针,我们定义一个变量int i = 10
,这时其实就是在内存中开辟一个4字节的空间,这个空间存放了数字10,然后使用一个别名i去指向这个空间的值。
上面我们可以通过&拿到某个对象的地址,那么当我们知道某个地址时,怎么获取它对应的值呢?
int main() {
int i = 100;
double d = 200;
// 1. 直接拿到值
printf("i 的值是 %d\n", i);
printf("d 的值是 %lf\n", d);
printf("\n");
// 2. 指针取出值, 既然万物皆地址,可以通过地址拿到值
// 通过&获取内存地址, 比如 &i 为1000DF
// 再通过*获取地址对应的值, &i 对应的值就是100
printf("i 的值是 %d\n", *(&i));
printf("d 的值是 %lf\n", *(&d));
}
使用符号*就可以获取某个地址的值,*比如(&i)
,获取了i对应的内存地址,在通过*(&i)
,获取对应得值,结果打印出来就是i的值。
i 的值是 100
d 的值是 200.000000
i 的值是 100
d 的值是 200.000000
指针变量
我们可以使用指针变量直接接收某个值的地址,在变量前面加上符号*:
int i = 100;
double d = 200l
int *intP = &i;
double *doubleP = &d;
printf("i 的值是 %d\n", *intP);
printf("d 的值是 %lf\n", *doubleP);
// 直接打印地址
printf("i 的地址是 %p\n", intP);
打印结果:
i 的值是 100
d 的值是 200.000000
i 的地址是 0x7ffedfd9a768
通过指针修改值
int main4() {
int i = 100;
int *pi = &i;
printf("i === %d\n", i);
// 直接修改i的值
i = 200;
printf("i === %d\n", i);
// 直接修改i的值
*pi = 300;
printf("i === %d\n", i);
return 0;
}
首先定义一个变量i = 100, pi指针指向i的地址,这时修改i的值有两种方式:
- 直接修改: i = 200;
- 通过指针修改: pi = 300,其实
*pi
就获取到了地址值,再进行修改
定义一个函数修改变量
以上原理明白了,那么定义一个函数修改当前变量是不是感觉很简单了
void changeVal(int i);
void changeP(int *i);
int main5() {
int i = 1;
printf("i point address === %p\n", &i);
// 直接传值,是不能修改i的值的
changeVal(i);
printf("i === %d\n", i);
// 通过地址修改值之后 i === 200
changeP(&i);
printf("i === %d\n", i);
}
// 当函数入栈时,会在内存开辟一个i的空间,
// 这时跟传递进来的i的地址是不一样的,修改了这个数是不起作用的\
// 通过打印两个i的地址即可证实
// i point address === 000000000061FE1C
// changVal i point address === 000000000061FDF0
void changeVal(int i) {
i = 300;
printf("changVal i point address === %p\n", &i);
}
// 把i的地址传递到这个函数中来, 在通过地址修改值
// 这时在上面打印就是修改过后的值了
void changeP(int *i) {
*i = 200;
}
当直接给函数传值是不能修改当前变量的,只能传递地址进来才可以修改。
看一下打印结果:
/Users/kangfan/work/Cwork/study_1/cmake-build-debug/study_1
i point address === 0x7ffedff72768
changVal i point address === 0x7ffedff7272c
i === 1
i === 200
Process finished with exit code 0
main函数中i的地址是0x7ffedff72768,而直接通过传值进来的i,地址变成了0x7ffedff7272c。
进阶:交换两个变量的值
下面进行进阶,定义一个函数,交换两个变量的值:
// 先定义一个tmp,找到a地址的值,
// a地址的值赋值为b
// b地址的值再赋值为tmp即可.
void swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
首先函数还是需要接受地址的
- 1,定义一个临时变量tmp,赋值为a的值
- 2,将a的地址的值改变为b的值
- 3,载将b的地址的值改变为tmp即可。
下面我们验证一下:
void swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
// 进阶: 根据以上原理,定义一个函数,交换两个int的值?
int a = 10;
int b = 20;
swap(&a, &b); // 交换
printf("a === %d\n", a);//a === 20
printf("b === %d\n", b);//b === 10
return 0;
}
打印结果:
a === 20
b === 10
总结
既然是入门,今天就少学习一点,到这里还是比较基础的,了解了指针,也才算是对c语言刚刚入门,可谓是学海无涯,付出和回报一定是成正比的,加油!