1. C语言基础知识
1.1 Linux下的第一个C程序
- 创建hello.c文件
vim hello.c
- 编辑文件
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
return 0;
}
- 编译代码
gcc hello.c
- 执行程序
./a.out
- 输出 Hello,World!
1.2 C语言数据类型(64位编译器)
- 整型
short (2个字节)
int(4个字节)/unsigned int (4个字节)
long(8个字节)/unsigned long(8个字节) - 浮点型
float (4个字节) 单精度
double(8个字节) 双精度 - 字符型
char (1个字节) - 构造类型
数组、结构体、共用体、枚举 - 指针类型(*代表地址里面的值,&代表取地址)
通过指针定义字符串:c语言定义字符串方法:char *p=“xikang”;(可以说p是字符串,但实际上p只是应该字符指针,本质上就是一个指针变量,只是p指向了一个字符串的起始地址而已。 - 空类型(void)
- 字符串常用操作:
求长度:strlen();
字符串连接:strcat();
比较字符串:strcmp();
字符串复制:strcpy();
1.3 标识符
- 标识符是由字母、数字、下划线组成;
- 第一个必须为字母或者下划线,且严格区分大小写。
- 标识符分为 关键字(if、else)、预定义标识符(define、scanf、printf、include)、用户标识符(xikang)。
1.4 关键字(重要)
static 的用法:
1) 用static 修饰局部变量:改变存储方式。变为静态存储方式,该变量在函数执行结束不会被释放,仍然保留在内存中。
2) 用static 修饰全局变量:改变其可见性。使其只在本文件内部有效。
3) 用static 修饰函数: 改变其连接方式。使函数只在本文件内部有效,不会与其他文件中的同名函数相冲突。
const 的用法:
1) 用const修饰常量: 定义时就初始化,以后不能更改。
2) 用const修饰形参: func(const int a) {}; 该形参在函数里不能改变。
3) 用const修饰类成员函数: 该函数对成员变量只能进行只读操作。
2. 输入输出
2.1 头文件
stdio.h 是一个头文件 (标准输入输出头文件) , #include 是一个预处理命令,用来引入头文件。 当编译器遇到 printf() 函数时,如果没有找到 stdio.h 头文件,会发生编译错误。
2.2 getchar() & putchar() 函数
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。可以在循环内使用这个方法,从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。可以在循环内使用这个方法,在屏幕上输出多个字符。
#include <stdio.h>
int main( )
{
int c;
printf( "Enter a value :");
c = getchar( );
printf( "\nYou entered: ");
putchar( c );
printf( "\n");
return 0;
}
2.3 gets() & puts() 函数:
char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符。
int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。
#include <stdio.h>
int main( )
{
char str[100];
printf( "Enter a value :");
gets( str );
printf( "\nYou entered: ");
puts( str );
return 0;
}
2.4 printf() & scanf() 函数
2.4.1 printf
- 使用前加头文件#include<stdio.h>
- 格式说明:printf(“格式控制”,“输出内容”),第一部分把第二部分以指定形式展现出来。输出内容可以为空。
格式字符 | 字符效果 (大写即输出英文为大写) |
---|---|
d | 有符号的的十进制 |
u | 无符号的的十进制 |
o | 无符号的八进制 |
x,X | 无符号的十六进制 |
f | 浮点数,小数形式输出 |
%.x f | 浮点数保留x位小数输出 |
e,E | 浮点数,指数形式输出 |
g,G | 浮点数,取e,f较短的输出,自动舍去末尾0 |
c | 单个字符 |
s | 输入字符串 |
示例1:
printf("%d, \n %d",12,21)
输出:
12
21
示例2:
char s[6] = "abcde";
printf("%s\n",s);
printf("%s","abcde");
输出:
abcde
abcde
2.4.2 scanf
- 格式说明: scanf(“格式控制”,地址表列);
- 格式控制与printf基本一致,不一样的是scanf地址表列不能为空,对于每一个格式控制的输入都要有相应的变量对应,而且每一个输入变量前必须有一个&(取地址符)
示例1:
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
输出:
1,2,3
示例2:
char s1[255],s2[255];
scanf("%s%s",&s1,&s2);
printf("%s\n%s",s1,s2);
输入:abc def
输出
abc
def
示例3:(指定输入长度)
int x,y,z
scanf("%2d%4d%d", &x,&y,&z);
printf("%d,%d,%d", x,y,z);
输入:1234567
输出:12,3456,7
输入: 1空格234567
输出:1,2345,67
3.表达式、数据结构
3.1 表达式
3.1.1 赋值表达式
符号 | 含义 | 实例 |
---|---|---|
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
3.1.2 算数表达式
加减乘除、取余、取模、自增、自减。
3.1.3 关系表达式
要么为真(1),要么为假(0)。
示例:
int x=1,y=0,z=2;
printf(x<y<z);
输出1;
先判断1<0,结果是0,再判断0<2,结果是1。
3.1.4 逻辑表达式
逻辑与: &&
位与: &
逻辑或: ||
位或: |
非 :!
3.2 数据结构
switch语句
有无break的区别,有break,匹配一个case,直接跳出,其他都不执行;无break,一个匹配,剩下全部执行。
4.函数
4.1 函数声明与调用
当您在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。需要在调用函数的文件顶部声明函数。
示例:
#include <stdio.h>
/* 函数声明 */
int max(int num1, int num2);
int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
/* 调用函数来获取最大值 */
ret = max(a, b);
printf( "Max value is : %d\n", ret );
return 0;
}
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
4.2 函数参数
- 函数的默认返回值类型是int。
- 函数参数可以是常量、变量、表达式、函数调用(递归、自己调用自己)
调用类型 | 描述
----------------------------| -------
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
引用调用 |通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
默认情况下,C 使用传值调用来传递参数。
4.3 常用库函数
- abs() 取绝对值
- sqrt() 开平方
- fabs() 小数取绝对值
- pow() 指数
- sin() 正弦
5.指针
指针变量的本质用来存放地址,一般变量存放数值。
5.1 指针的定义(重要)
方式1:
int x=3; int *p=&x;
方式2:
int x=3;int *p;
p=&x;
*p是数值,p是地址,可以用在scanf函数中:
scanf("%d",p);
- **p++ 和 (p)++的区别:前者地址会变,先进行地址+1,再取值。
后者地址不会变,先去当前地址值,再将值+1。
示例:
#include<stdio.h>
int main(){
int *p,m[] ={1,3,5,7,9};
p=m;
int a=*p++;
int b=(*p++);
int c=((*p)++);
int d=(*p)++;
printf("%d,\n %d,\n %d,\n %d",a,b,c,d);
return 0;
}
输出: 1,
3,
5,
6
5.2 二级指针
*p : 一级指针,存放变量的地址。
**q : 二级指针,存放一级指针的地址。
示例:
int x=7;
int *p=&x, **q=p;
输出:
*p=7,*q=p,**q=7;
5.3 移动指针遍历字符串
char *s="xikang";
while(*s){ //
printf("%c",*s);
s++;
}
指向的内容不为空则进行循环,s代表地址,地址在变化。
5.4 指针在函数值传递与地址传递之间的应用(重要)
示例:交换两个数的值。
方法一:使用值传递,方法需要有返回值。
错误写法:
#include<stdio.h>
void fun(int a ,int b){
int t;
t=a;
a=b;
b=t;
}
main(){
int x=1;
int y=3;
fun(x,y);
printf("%d,%d",x,y);
}
原因在与函数fun()无返回值,在主函数调用时,仅仅对形参a,b进行了交换,并没有将交换后的值传递给实参x,y。
方法二:使用指针进行交换。
#include<stdio.h>
void fun(int *a ,int *b){
int t;
t=*a;
*a=*b;
*b=t;
}
main(){
int x=1;
int y=3;
fun(&x,&y);//实参
printf("%d,%d",x,y);
}
5.5 函数指针
函数指针本质上是一个指针,相对于常用的普通函数定义,其实就是把它函数名部分用指针来代替。常用作回调函数。
char (*fun)(char); //定义一个函数指针
char fun_1(char x) //定义一个函数体
{
//函数内容
return 0;
}
fun= & fun_1 //函数体与指针相关联
5.6 指针函数
一个返回指针的函数,其本质是一个函数。常用于返回数组、字符串等数据结构指针。
写法:
int *fun(int x,int y);
6.数组
6.1 一维数组初始化
int a[5]={1,2,3,4,5};合法
int a[5]={1,2,3,};合法
int a[ ]={1,2,3,4,5};合法
int a[5]={1,2,3,4,5,6};不合法
int a[5];合法
int a[1+1];合法
int x=5,int a [x];不合法,数组个数必须是常量。
define P 5 int a[P]; 合法,P是符号常量。
6.2 一维数组的遍历
- 求数组长度sizeof(arr)/sizeof(arr[0];strlen(char);
以下代码用于求取数组所有元素的和、最大值、最小值以及平均值。
#include<stdio.h>
main(){
int nums[] = {11,12,44,56,66};
int sum=0,avg=0,max=0,min=100;//默认数组中取值范围为0-100
int *p;//定义指针变量
int len =sizeof(nums)/sizeof(nums[0]);
for(p=&(nums[0]);p<=&(nums[len-1]);p++){
sum+=*p;
if(*p>max){
max=*p;
}
if(*p<min){
min=*p;
}
}
printf("sum=%d avg=%f",sum,sum*1.0/len);
printf("max=%d min=%f",max,min);
}
6.3 二维数组初始化
int a[2][3]={1,2,3,4,5,6};合法
int a[2][3]={1,2,3,4,5, };合法
int a[2][3]={{1,2,3} {4,5,6}};合法
int a[2][3]={{1,2,3} {4,5, }};合法
int a[ ][3]={1,2,3,4,5,6};合法
int a[ ][3]={1,2,3,4,5,6};合法
int a[2][ ]={1,2,3,4,5,6};不合法,可以缺少行个的个数,不可以缺少列的个数。
- a[2]用指针表示*(a+2);
- a[2][3]用指针表示*(a+2)[3] 或者*(*(a+2)+3)
6.4 二维数组的遍历
四种方式遍历二维数组:
a[i][j]、* (a[i]+j)、* (*(a+i)+j)、*(&a[0][0]+i*n+j)
#include <stdio.h>
void main(){
int i,j;
int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
printf("方式一:a[i][j]\n"); //方式一
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf("%d",a[i][j]);
}
}
printf("\n方式二:*(a[i]+j)\n"); //方式二
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf("%d",*(a[i]+j));
}
}
printf("\n方式三:*(*(a+i)+j)\n"); //方式三
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf("%d",*(*(a+i)+j));
}
}
printf("\n方式四:*(&a[0][0]+i*3+j)\n"); //方式四
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf("%d",*(&a[0][0]+i*3+j));
}
}
printf("\n");
}
7 内存分配问题
内存分区示意图:
7.1 栈区
栈区介绍
- 栈区由编译器自动分配释放,由操作系统自动管理,无须手动管理。
- 栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。
- 栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
- 栈区是先进后出原则。
存放内容
-
临时创建的局部变量和const定义的局部变量存放在栈区。
-
函数调用和返回时,其入口参数和返回值存放在栈区。
7.2 堆
堆区介绍
-
堆区由程序员分配内存和释放。
-
堆区按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
-
分配内存使用malloc函数。
void *malloc(size_t);
返回值是一个void*型的指针,该指针指向分配空间的首地址。
- 释放内存使用free函数。
void free(void * /*ptr*/);
参数是开辟的内存的首地址。
存放内容
- 存放使用malloc创建的对象。
7.3 全局区/静态存储区
- 通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。
- 全局区有 .bss段 和 .data段组成,可读可写。
7.4 常量区
- 字符串、数字等常量存放在常量区。
- const修饰的全局变量存放在常量区。
- 程序运行期间,常量区的内容不可以被修改。
7.5 代码区
- 程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。
- 字符串常量和define定义的常量也有可能存放在代码区。