DAY01
- 概述(学习要求、学习标准)
- 数据类型和变量
- 内存四区(栈、堆、全局、代码区)
1 上午学习知识点记录
1.1 接口的封装和设计
- 接口api的使用能力
- 接口api的查找能力
- 接口api的实现能力
一般来说,大型项目都有API手册,如果没有标准API手册,查看头文件。查看api头文件
、函数名
、功能
、参数
、返回值
。
1.2 建立正确程序运行内存布局图
- 内存四区模型
- 函数调用模型
1.3 了解CS和BS
C/S:客户端服务器
B/S:浏览器和服务器(可以认为B/S是特殊的C/S)
1.4 防止头文件重复包含
/* Way 1. */
#pragma once
...
/* Way 2. */
#ifndef _INCLUDE_H__
#define _INCLUDE_H__
....
#endif
兼容C++编译器
/* 头文件函数声明 */
/* 防止头文件重复包含 */
#pragma once
/* 兼容C++编译器
* 如果是C++编译器,使用extern "C" 按C标准编译
* extern "C"是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数
* __cplusplus是C++编译器已经定义的宏,C语言中没有该定义
*/
#ifdef __cplusplus
extern "C"{
#endif
/**
* 第一套接口
*/
/* 初始化环境 */
int socketclient_init(void **handle);
/* 发送信息 */
int socketclient_send(void *handle, void *buf, int len);
/* 接受信息 */
int socketclient_recv(void *handle, void *buf, int len);
/* 释放资源 */
int socketclient_destroy(void *handle);
#ifdef __cplusplus
}
#endif
#ifndef
方式是C/C++语言的标准支持,也是比较常用的方式。编译大型项目时间相对较长。(更加灵活,兼容性好)
#pragma once
一般由编译器提供保证,同一个文件不会被包含多次。编译大型项目效率高。有些编译器不支持(较老编译器不支持,如GCC3.4版本之前不支持,操作简单,效率高)。
1.5 数组形参退化为指针
- 形参:在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
- 实参:函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
- 如果数组作为函数参数,数组形参会退化为指针,这也就说明调用函数的时候为什么要传入数组或者字符串长度。
void print_array(int a[1], int n)
void print_array(int a[], int n)
void print_array(int *a, int n)
1.6 数据类型本质
数据类型其实就是一种固定内存大小的别名,例如int就是4个字节。这样就可以告诉编译器给int a中的a变量分配多少内存空间。
注意b
和&b
不同。
- 数据类型可理解为创建变量的模具:
是固定内存大小的别名
。 - 数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
- 注意:数据类型只是模具,编译器并没有分配空间,只有根据类型(模具)创建变量(实物),编译器才会分配空间。
int a;
int b[10];
/**
* 打印地址
* 数组名字就是数组首元素地址,数组首地址
*/
printf("b:%d, &b:%d\n", b, &b);
/**
* b,&b的代表的数据类型不一样
* b :**数组首元素**地址,一个元素4字节,+1就是地址+4(int类型指针步长是4)
* &b:**整个数组**的首地址,一个数组4*10 = 40字节,+1就是地址+40
*/
printf("b + 1: %d, &b: %d\n", b+1, &b+1);
打印结果:
sizeof(a) = 4, sizeof(b) = 40
b:0x7fdecdf9f0, &b:0x7fdecdf9f0
b + 1: 0x7fdecdf9f4, &b: 0x7fdecdfa18
1.7 数据类型的别名
通常使用typedef
给结构体或者数据类型起别名
typedef unsigned int u32
typedef struct _UI UI
struct _UI{
char name[64];
int add;
};
1.8 数据类型的封装
- 数据类型的封装。
int InitHardEnv(void **handle);
void *memcpy(void *Dest, const void *src, size_t len)
- void修饰函数返回值和参数,仅表示无。
void fun(void){
printf("VOID\n");
}
1.9 数据类型的总结和拓展
- 数据类型本质是固定内存大小的别名,是个模具,C语言规定:通过数据类型定义变量。
- 数据类型大小计算
sizeof
- 可以给已存在的数据类型起别名
typedef
- 数据类型封装概念(void万能类型)
2 下午学习记录
2.1 变量的本质分析
2.2.1 变量的概念
概念:既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称为常量。
2.2.2 变量的本质
一段连续内存空间的别名
- 程序通过变量来申请和命名内存空间 int a = 0;
- 通过变量名访问内存空间。
2.3 程序的内存四区模型
流程说明:
- 操作系统把物理硬盘代码加载到内存中。
- 操作系统把C代码分成四个区。
栈区(stack) | 由编译器自动分配释放,存放函数的参数值,局部变量的值等 |
---|---|
堆区 (heap) | 一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收 |
全局区(静态区)(static) | 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。静态全局变量,静态局部变量,是全局变量 |
常量区 | 字符串常量和其他常量的存储位置,程序结束后由操作系统释放。"字符串" |
程序代码区 | 存放函数体的二进制代码。 |
- 注意:
const
修饰的不是常量,确切来说是只读量,具体在那个区域就看怎样定义了。 - 未被初始化的全局变量和static修饰的变量,系统自动赋值为0。
2.3.1 全局区分析
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *
get_str1(){
/* 文字常量区 */
char *p = "abcdef";
printf("sizeof_size = %ld\n", sizeof("abcdef"));
return p;
}
char *
get_str2(){
/* 文字常量区 */
char *p = "abcdef";
printf("sizeof_size = %ld\n", sizeof("abcdef"));
return p;
}
int
main(int argc, char *argv[]){
char *p = NULL;
char *q = NULL;
p = get_str1();
printf("p = %s, p = %p\n", p, p);
q = get_str2();
printf("q = %s, q = %p\n", q, q);
char *t = "abcdef";
printf("t = %s, t = %p\n", t, t);
return EXIT_SUCCESS;
}
打印结果:
sizeof_size = 7
p = abcdef, p = 0x5571439900
sizeof_size = 7
q = abcdef, q = 0x5571439900
t = abcdef, t = 0x5571439900
2.4 栈的生长方向和内存存放方向
打印结果:
&a = 000000000061fe1c
&b = 000000000061fe18
&buf = 000000000061fe10
2.5 作用域与生命周期
- 作用域(scope):是一个静态概念,只能在编译源程序的时候用到。一个标识符的作用域指在源文件中该标识符能够独立地合法出现的区域。
- 生命周期(duration):是一个运行时概念,它是指一个变量在整个程序从载入到结束运行的过程中存在的时间周期。
2.5 关键字static
- 修饰全局变量或者函数时,该变量只能在本文件内使用
- 修饰局部变量时,如果该局部变量没有被初始化,其值为0;
- 修饰的局部变量只会被初始化一次
局部静态(static)变量,作用域为局部,而生命周期是全程。 静态局部变量属于静态存储方式,它具有以下特点: (1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。 (2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
2.6 数组赋值字符串问题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *
get_str(){
/* 这个是数组,不是 char *str = "abcdedsgs" */
/**
* "abcdedsgs\0"字符串会先存放在全局区
* 赋值给 str[] 时候,编译器会拷贝 "abcdedsgs\0" 给数组str[];
* 因为,返回的str是栈区数组地址,并不是文字常量区的"abcdedsgs\0"地址。
*/
//static char str[] = "abcdedsgs"; /* 栈区 */
char str[] = "abcdedsgs"; /* 栈区 */
return str;
}
char *
get_str2(){
char *p = (char *)malloc(sizeof("hello"));
if(p == NULL){
return NULL;
}
strcpy(p, "hello");
return p;
}
int
main(int argc, char *argv[]){
char buf[128] = {0};
strcpy(buf, get_str());
printf("buf = %s\n", buf); /* 乱码,不确定,段错误不能运行 */
char *p = NULL;
p = get_str2();
if(p != NULL){
printf("p = %s\n", p);
free(p);
/* 释放完内存后,最后赋值指针指向NULL,避免二次释放引发错误 */
p = NULL;
if(p != NULL){
free(p);
}
}
return EXIT_SUCCESS;
}
3 知识点复习
3.1 学习标准
/* 初始化环境 */
int socketclient_init(void **handle);
/* 发送信息 */
int socketclient_send(void *handle, void *buf, int len);
/* 接受信息 */
int socketclient_recv(void *handle, void *buf, int len);
/* 释放资源 */
int socketclient_destroy(void *handle);
3.2 听课标准
(1)选择法排序
(2)会简单的封装函数
(3)数组做函数参数会退化为一级指针
a.数组做函数参数时,应该把数组元素个数也传递给函数(因为此时数组已经退化成指针,不能知道数组的长度)。
b.形参中的数组,编译器把它当作指针处理,C语言的特色。
c.实参中的数组,和形参中的数组本质不一样。
void fun(int a[]); /* 4字节 */
main(){
int a[] = {1, 2, 3} /* 12字节 */
fun(a);
}
3.2 数据类型
-
类型的本质:固定内存大小的别名
-
数据类型的作用:编译器预算对象(变量)分配的内存空间大小
int a; 告诉C编译器分配4个字节的内存 -
数据类型可以typedef起别名
-
可以通过sizeof( )测类型大小
-
void数据类型(无类型,万能类型)
a.如果函数没有返回值,必须用void修饰: void fun(int a);
b.如果函数没有参数,参数可以用void修饰:int fun(void);
c.不能定义void类型的普通变量:void a;(不能确定分配多大空间)
d. void *p; 万能指针
int b[10];
/* b, &b的数组类型不一样
* b, 数组首元素地址,一个元素4字节, +1, +4
* &b,整个数组的首地址,一个数组4×10=40字节, +1, +40ta
*/
4 作业
#include <stdio.h>
/**
* 1.数据类型的本质是什么
* 答:固定内存大小的别名
*/
/**
* 2.如何给一个数据类型起别名
* 答:typedef unsigned int u32
*/
/**
* 3.既然有栈空间,为何要有堆空间?
* 答:栈空间是系统自动回收,堆空间用户管理
*/
/* 4.有一个函数,数组做函数参数 */
void
print_array(int a[128]){
//sizeof(a) = 8 32 位 4
// 64 位 8
}
/* 5.有一个数组,如何求出元素个数 */
/*int
main(void){
int a[] = {1, 3, 3, 2, 8};
printf("leng = %ld\n", sizeof(a)/sizeof(a[0]));
}
*/
/**
* 6.在32位平台上
* char ************p = NULL;
* int *q = NULL;
* sizeof(p) = 4;
* sizeof(q) = 4;
*/
/* 7.画出下面代码的内存四区图 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *get_mem(int size)
{
char *p2 = NULL; //分配4个字节的内存 栈区也叫临时区
p2 = (char *)malloc(size);
return p2;
}
int main(void)
{
char buf[100];
int a = 10; //分配4个字节的内存 栈区也叫临时区
int *p; //分配4个字节的内存
p = &a;
*p = 20;
char *mp = get_mem(100);
strcpy(mp, "ABCDEFG");
if (mp != NULL)
{
free(mp);
mp = NULL;
}
return 0;
}
2 指针
2.1 指针强化
全局区 什么时候释放???
static和cons