曾大稳丶


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

结合ffmpeg用SDL播放YUV实现简易播放器

发表于 2018-06-20 | 分类于 ffmpeg |
字数统计: 1,106字 | 阅读时长 ≈ 6分钟

通过解码之后得到的yuv视频数据我们直接可以进行播放,本篇使用SDL来实现视频播放。

SDL环境配置:https://blog.csdn.net/furzoom/article/details/53992124

SDL视频播放基础教程: https://blog.csdn.net/xuyankuanrong/article/details/77574152

有了上一篇文章的解码的基础,代码看着就很轻松了。

代码上写的很详细了,具体看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250


#include "stdafx.h"
#include <iostream>

// LNK2019 无法解析的外部符号 SDL_main,该符号在函数 main_utf8 中被引用
#define SDL_MAIN_HANDLED

//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libsdl/SDL.h>
}



void log_s(const char * msg, int d = -1123) {
if (d == -1123) {
printf_s("%s\n", msg);
}
else {
printf_s("%s %d \n", msg, d);
}
}


int thread_exit = 0;
int thread_pause = 0;

int sfp_refresh_thread(void *opaque) {
while (!thread_exit) {
if (!thread_pause) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(10);
}
thread_exit = 0;
thread_pause = 0;
//Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);

return 0;
}

int play(const char* filePath) {
AVFormatContext * pFmtCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVFrame *pFrame = NULL;
AVFrame *pFrameYUV = NULL;
uint8_t *outBuffer = NULL;
AVPacket *pPacket = NULL;
SwsContext *pSwsCtx = NULL;

//SDL
int screen_w, screen_h;
SDL_Window *screen=NULL;
SDL_Renderer* sdlRenderer = NULL;
SDL_Texture* sdlTexture = NULL;
SDL_Rect sdlRect;
SDL_Thread *video_tid = NULL;
SDL_Event event;


//1. 初始化
av_register_all();
avformat_network_init();
//2. AVFormatContext获取
pFmtCtx = avformat_alloc_context();
//3. 打开文件
if (avformat_open_input(&pFmtCtx, filePath, NULL, NULL) != 0) {
log_s("Couldn't open input stream.\n");
return -1;
}
//4. 获取文件信息
if (avformat_find_stream_info(pFmtCtx, NULL)<0) {
log_s("Couldn't find stream information.");
return -1;
}
//5. 获取视频的index
int i = 0, videoIndex = -1;
for (; i < pFmtCtx->nb_streams; i++) {
if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}

if (videoIndex == -1) {
log_s("Didn't find a video stream.");
return -1;
}
//6. 获取解码器并打开
pCodecCtx = avcodec_alloc_context3(NULL);
if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
log_s("Didn't parameters to contex.");
return -1;
}
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
log_s("Codec not found.");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
log_s("Could not open codec.");
return -1;
}
//7. 解码播放开始准备工作
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();

//根据需要解码的类型,获取需要的buffer,不要忘记free
outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1) * sizeof(uint8_t));
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

pPacket = av_packet_alloc();
log_s("--------------- File Information ----------------");
av_dump_format(pFmtCtx, 0, filePath, 0);
log_s("-------------------------------------------------");
//获取SwsContext
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);
//----------------------------------------------------------------------------------------------------------------
// 8. SDL相关初始化
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
log_s("Could not initialize SDL - ");
log_s(SDL_GetError());
return -1;
}
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
screen = SDL_CreateWindow("WS ffmpeg player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
pCodecCtx->width/2, pCodecCtx->height/2, SDL_WINDOW_OPENGL);
if (!screen) {
log_s("SDL: could not create window - exiting");
log_s(SDL_GetError());
return -1;
}

sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);

sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;

video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//----------------------------------------------------------------------------------------------------------------


int count = 0;

//9.读取数据播放
for (;;) {
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT) {
if (av_read_frame(pFmtCtx, pPacket) == 0) {
if (pPacket->stream_index == videoIndex) {
if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
log_s("Decode end or Error.");
break;
}
else {//处理解码数据
avcodec_receive_frame(pCodecCtx, pFrame);

if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize) == 0) {
continue;
}

count++;

//SDL播放---------------------------
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(sdlRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect);
SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect, NULL);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------

log_s("Succeed to play frame!", count);
}
}
}else {
//退出线程
thread_exit = 1;
av_packet_unref(pPacket);
}
}
else if (event.type == SDL_KEYDOWN) {
log_s("Pause");
//Pause
if (event.key.keysym.sym == SDLK_SPACE)
thread_pause = !thread_pause;
}
else if (event.type == SDL_QUIT) {
log_s("quit");
thread_exit = 1;
break;
}
else if (event.type == SFM_BREAK_EVENT) {
log_s("break");
break;
}
}
//sdl退出
SDL_Quit();

//回收
if (pSwsCtx != NULL) {
sws_freeContext(pSwsCtx);
}
if (outBuffer != NULL) {
av_free(outBuffer);
}
if (pFrameYUV != NULL) {
av_frame_free(&pFrameYUV);
}
if (pFrame != NULL) {
av_frame_free(&pFrame);
}
if (pCodecCtx != NULL) {
avcodec_close(pCodecCtx);
}
if (pFmtCtx != NULL) {
avformat_close_input(&pFmtCtx);
}
}


int main()
{
play("F:/视频资源/gxsp.mp4");
getchar();
return 0;
}

参考链接:https://blog.csdn.net/king1425/article/details/71171142

ffmpeg 视频解码h264和yuv

发表于 2018-06-20 | 分类于 ffmpeg |
字数统计: 1,286字 | 阅读时长 ≈ 7分钟

之前学习 ffmpeg 在 android 平台上,发现很不方便,所以打算在 vs 上重新搭建环境,然后重新学习,之后如果需要用到的话在移植到其他平台。环境搭建参考的是: https://blog.csdn.net/weixinhum/article/details/37699025

环境

Microsoft Visual C++ 2017
vs2017
ffmpeg 3.4.2

步骤主要是以下几大步骤:

  1. 初始化
1
2
av_register_all();
avformat_network_init();
  1. AVFormatContext获取和初始化
1
2
3
4
5
6
//AVFormatContext获取
avformat_alloc_context()
//打开文件,和AVFormatContext关联
avformat_open_input()
//获取文件流信息
avformat_find_stream_info()
  1. 获取解码器
1
2
3
4
5
6
7
8
//AVCodecContext获取
avcodec_alloc_context3()
//将AVCodecParameters转换为AVCodecContext
avcodec_parameters_to_context()
//获取解码器
avcodec_find_decoder()
//打开解码器
avcodec_open2()
  1. 解码准备
1
2
3
4
5
6
7
8
9
10
//获取解码数据包装 AVFrame
av_frame_alloc()
//根据宽高,解码类型(yuv420)获取缓存buffer大小
av_image_get_buffer_size()
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的AVFrame里面
av_image_fill_arrays()
//获取编码数据 包装 AVPacket
av_packet_alloc()
//获取SwsContext 图片转换(宽高这些)需要用到
sws_getContext()
  1. 读取数据源解码存储
1
2
3
4
5
6
7
8
9
10
//读取编码数据源到AVPacket
av_read_frame()
//发送数据源
avcodec_send_packet()
//解码数据源 ,和avcodec_send_packet配合使用
avcodec_receive_frame()
//图像转换
sws_scale()
//写入文件
fwrite()
  1. 回收

具体代码和步骤如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include <iostream>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

//初始化
void init() {
av_register_all();
avformat_network_init();
}

void log(const char * msg, int d=-1123) {
if (d == -1123) {
printf_s("%s\n", msg);
}
else {
printf_s("%s %d \n", msg ,d);
}

}


int video2YuvAndH264(const char * filePath,FILE * yuvFilePath, FILE * h264FilePath){
AVFormatContext * pFmtCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVFrame *pFrame = NULL;
AVFrame *pFrameYUV = NULL;
uint8_t *outBuffer = NULL;
AVPacket *pPacket = NULL;
SwsContext *pSwsCtx = NULL;

//1. 初始化
init();
//2. AVFormatContext获取
pFmtCtx = avformat_alloc_context();
//3. 打开文件
if (avformat_open_input(&pFmtCtx,filePath,NULL,NULL)!=0) {
log("Couldn't open input stream.\n");
return -1;
}
//4. 获取文件信息
if (avformat_find_stream_info(pFmtCtx,NULL)<0) {
log("Couldn't find stream information.");
return -1;
}
//5. 获取视频的index
int i = 0,videoIndex =-1;
for (; i < pFmtCtx->nb_streams; i++) {
if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}

if (videoIndex == -1) {
log("Didn't find a video stream.");
return -1;
}
//6. 获取解码器并打开
pCodecCtx = avcodec_alloc_context3(NULL);
if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
log("Didn't parameters to contex.");
return -1;
}
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
log("Codec not found.");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
log("Could not open codec.");
return -1;
}
//7. 解码开始准备工作
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();

//根据需要解码的类型,获取需要的buffer,不要忘记free
outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)*sizeof(uint8_t));
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);

pPacket = av_packet_alloc();
log("--------------- File Information ----------------");
av_dump_format(pFmtCtx, 0, filePath, 0);
log("-------------------------------------------------");
//获取SwsContext
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);

int count=0;
//8. 读取数据
while (av_read_frame(pFmtCtx, pPacket) == 0) {//读取一帧压缩数据
if (pPacket->stream_index == videoIndex) {
//写入H264数据到文件
fwrite(pPacket->data, 1, pPacket->size, h264FilePath); //把H264数据写入h264FilePath文件
//解码数据
//avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pPacket);
if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
log("Decode end or Error.\n");
break;
}else {//处理解码数据并写入文件
avcodec_receive_frame(pCodecCtx,pFrame);

if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize) == 0) {
continue;
}

count++;
int y_size = pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, y_size, yuvFilePath); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, yuvFilePath); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, yuvFilePath); //V
log("Succeed to decode frame!",count);
}
}
av_packet_unref(pPacket);
}
//flush decoder
/*当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。
因此需要通过“flush_decoder”将这几帧数据输出。
“flush_decoder”功能简而言之即直接调用avcodec_decode_video2()获得AVFrame,而不再向解码器传递AVPacket。*/
while (1) {
if (avcodec_send_packet(pCodecCtx, pPacket)!= 0) {
log("Decode end or Error.\n");
break;
}
else {
avcodec_receive_frame(pCodecCtx, pFrame);

sws_scale(pSwsCtx, (const unsigned char *const *)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);

int y_size = pCodecCtx->width * pCodecCtx->height;
// yuv-> 4:1:1
fwrite(pFrameYUV->data[0], 1, static_cast<size_t>(y_size), yuvFilePath); //Y
fwrite(pFrameYUV->data[1], 1, static_cast<size_t>(y_size / 4), yuvFilePath); //U
fwrite(pFrameYUV->data[2], 1, static_cast<size_t>(y_size / 4), yuvFilePath); //V
log("Flush Decoder: Succeed to decode frame!", count);
}
}

if (pSwsCtx != NULL) {
sws_freeContext(pSwsCtx);
}
if (outBuffer != NULL) {
av_free(outBuffer);
}
if (pFrameYUV != NULL) {
av_frame_free(&pFrameYUV);
}
if (pFrame != NULL) {
av_frame_free(&pFrame);
}
if (pCodecCtx != NULL) {
avcodec_close(pCodecCtx);
}
if (pFmtCtx != NULL) {
avformat_close_input(&pFmtCtx);
}

}


int main()
{
FILE * yuvFile;
FILE * h264File;
fopen_s(& yuvFile,"F:/视频资源/gxsp.yuv", "wb+");
fopen_s(& h264File,"F:/视频资源/gxsp.h264", "wb+");
video2YuvAndH264("F:/视频资源/gxsp.mp4", yuvFile, h264File);

fclose(yuvFile);
fclose(h264File);

getchar();

return 0;
}

在Android上进行视频解码

参考链接: https://blog.csdn.net/king1425/article/details/71160339

ffmpeg android 推送RTMP

发表于 2018-05-22 | 分类于 ffmpeg |
字数统计: 761字 | 阅读时长 ≈ 4分钟
  1. Nginx 搭建RTMP服务器
  2. android端代码以及步骤
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161

    static int publish_file_stream(const char *input, const char *output) {
    //注意这里要写=NULL 不然会崩溃
    AVFormatContext *pInAvFormatContext = NULL;
    AVFormatContext *pOutAvFormatContext = NULL;
    AVOutputFormat *pAvOutputFormat = NULL;
    int videoIndex = 0;

    //1. 注册
    av_register_all();
    LOGE("%s", output);

    //2. 获取输入的文件信息

    //打开文件
    if (avformat_open_input(&pInAvFormatContext, input, NULL, NULL) != 0) {
    LOGE("打开文件失败!");
    return -1;
    }

    //获取流信息
    if (avformat_find_stream_info(pInAvFormatContext, NULL) < 0) {
    LOGE("获取文件流失败!");
    return -1;
    }
    //找到video的对应位置
    int i = 0;
    for (; i < pInAvFormatContext->nb_streams; i++) {
    if (pInAvFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
    videoIndex = i;
    break;
    }
    }

    //打印信息
    // av_dump_format(pAvFormatContext,0,input,0);

    //3. 输出设置
    //初始化输出AVFormatContext获取 AVOutputFormat进行设置
    avformat_alloc_output_context2(&pOutAvFormatContext, NULL, "flv", output);
    if (!pOutAvFormatContext) {
    LOGE("初始化输出AVFormatContext失败");
    return -1;
    }

    pAvOutputFormat = pOutAvFormatContext->oformat;
    i = 0;
    for (; i < pInAvFormatContext->nb_streams; i++) {
    AVStream *in_stream = pInAvFormatContext->streams[i];
    AVStream *out_stream = avformat_new_stream(pOutAvFormatContext, in_stream->codec->codec);
    if (!out_stream) {
    LOGE("初始化out_stream失败");
    return -1;
    }
    //复制AVCodecContext的设置(Copy the settings of AVCodecContext)
    if (avcodec_copy_context(out_stream->codec, in_stream->codec) != 0) {
    LOGE("copy AVCodecContext设置失败");
    return -1;
    }

    out_stream->codec->codec_tag = 0;
    if (pOutAvFormatContext->oformat->flags & AVFMT_GLOBALHEADER)
    out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

    //打印
    // av_dump_format(pOutAvFormatContext,0,output,0);
    //打开输出文件/url
    if (!(pAvOutputFormat->flags & AVFMT_NOFILE)) {
    int ret = avio_open(&pOutAvFormatContext->pb, output, AVIO_FLAG_WRITE);
    if (ret < 0) {
    LOGE("打开输出文件或者url失败 %d", ret);
    return -1;
    }
    }

    //4.写入数据
    //写文件头
    if (avformat_write_header(pOutAvFormatContext, NULL) != 0) {
    LOGE("写入头数据失败");
    return -1;
    }
    int64_t start_time = av_gettime();
    AVPacket pkt;
    int frameIndex = 0;

    //写入数据源
    while (av_read_frame(pInAvFormatContext, &pkt) >= 0) {
    AVStream *in_stream, *out_stream;
    if (pkt.pts == AV_NOPTS_VALUE) {
    //Write PTS
    AVRational time_base1 = pInAvFormatContext->streams[videoIndex]->time_base;
    //Duration between 2 frames (us)
    int64_t calc_duration = (double) AV_TIME_BASE /
    av_q2d(pInAvFormatContext->streams[videoIndex]->r_frame_rate);
    //Parameters
    pkt.pts = (double) (frameIndex * calc_duration) /
    (double) (av_q2d(time_base1) * AV_TIME_BASE);
    pkt.dts = pkt.pts;
    pkt.duration = (double) calc_duration / (double) (av_q2d(time_base1) * AV_TIME_BASE);
    }

    //Important:Delay
    if (pkt.stream_index == videoIndex) {
    AVRational time_base = pInAvFormatContext->streams[videoIndex]->time_base;
    AVRational time_base_q = {1, AV_TIME_BASE};
    int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
    int64_t now_time = av_gettime() - start_time;
    if (pts_time > now_time)
    av_usleep(pts_time - now_time);

    }

    in_stream = pInAvFormatContext->streams[pkt.stream_index];
    out_stream = pOutAvFormatContext->streams[pkt.stream_index];

    /* copy packet */
    //转换PTS/DTS(Convert PTS/DTS)
    pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
    (AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
    (AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
    pkt.pos = -1;
    //Print to Screen
    if (pkt.stream_index == videoIndex) {
    LOGE("Send %8d video frames to output URL\n", frameIndex);
    frameIndex++;
    }

    if (av_interleaved_write_frame(pOutAvFormatContext, &pkt) < 0) {
    LOGE("Error muxing packet\n");
    break;
    }

    av_packet_unref(&pkt);
    }


    //写文件尾(Write file trailer)
    av_write_trailer(pOutAvFormatContext);
    avformat_close_input(&pInAvFormatContext);
    /* close output */
    if (pOutAvFormatContext && !(pAvOutputFormat->flags & AVFMT_NOFILE))
    avio_close(pOutAvFormatContext->pb);
    avformat_free_context(pOutAvFormatContext);
    return 0;
    }

    JNIEXPORT jint JNICALL
    Java_zzw_com_ffmpegdemo_VideoUtils_publish_1file_1stream(JNIEnv *env, jclass type, jstring input_,
    jstring output_) {
    const char *input = env->GetStringUTFChars(input_, 0);
    const char *output = env->GetStringUTFChars(output_, 0);
    //input: /storage/emulated/0/aaaaa/dst.mp4
    //output: rtmp://192.168.18.231:8082/live/room
    int ret = publish_file_stream(input, output);
    env->ReleaseStringUTFChars(input_, input);
    env->ReleaseStringUTFChars(output_, output);
    return ret;
    }

参考连接:

https://blog.csdn.net/leixiaohua1020/article/details/39803457

ffmpeg android音频解码

发表于 2018-05-12 | 分类于 ffmpeg |
字数统计: 885字 | 阅读时长 ≈ 5分钟

音频解码就是将mp3 aac等格式这些文件解析为pcm格式的过程。
和视频解码流程一样,只是有些函数不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include "lang.h";
#include <string>
//封装格式
//解码
#include "log.h"

extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

};

#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio

static int audio_decode_example(const char *input, const char *output) {
AVCodec *pCodec;
AVCodecContext *pCodecContext;
AVFormatContext *pFormatContext;
struct SwrContext *au_convert_ctx;


uint8_t *out_buffer;

//1. 注册
av_register_all();
//2.打开解码器 <-- 拿到解码器 <-- 拿到id <-- 拿到stream和拿到AVCodecContext <-- 拿到AVFormatContext

//2.1 拿到AVFormatContext
pFormatContext = avformat_alloc_context();
//2.1.1 打开文件
if (avformat_open_input(&pFormatContext, input, NULL, NULL) != 0) {
LOGE("打开文件失败!");
return -1;
}
//2.2 拿到AVCodecContext
//2.2.1 拿到流信息
if (avformat_find_stream_info(pFormatContext, NULL) < 0) {
LOGE("AVFormatContext获取流信息失败!");
return -1;
}
//打印信息
// av_dump_format(pFormatContext, 0, input, false);

//2.2.2 通过streams找到audio的索引下标 也就获取到了stream
int audioStream = -1;
int i = 0;
for (; i < pFormatContext->nb_streams; i++)
if (pFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStream = i;
break;
}

if (audioStream == -1) {
LOGE("AVMEDIA_TYPE_AUDIO索引没找到!");
return -1;
}
//2.2.3 获取到AVCodecContext
pCodecContext = pFormatContext->streams[audioStream]->codec;

//2.2.4 通过AVCodecContext拿到id ,拿到解码器
pCodec = avcodec_find_decoder(pCodecContext->codec_id);
if (pCodec == NULL) {
LOGE("AVCodec获取失败!");
return -1;
}
//2.2.5 打开解码器
if (avcodec_open2(pCodecContext, pCodec, NULL) < 0) {
LOGE("打开解码器失败!");
return -1;
}

//3. 解码 将解码数据封装在AVFrame <-- 拿到编码的数据AVPacket <-- 读取数据源 <-- 解码文件参数设置

//3.1 AVPacket初始化
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
av_init_packet(packet);

//3.2 解码文件参数设置
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;

//nb_samples: AAC-1024 MP3-1152
//音频帧中每个声道的采样数
int out_nb_samples = pCodecContext->frame_size;

//音频采样格式 量化精度
AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
//采样率
int out_sample_rate = 44100;
//声道
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);

//获取到 缓冲大小
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples,
out_sample_fmt, 1);
out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRAME_SIZE * 2);

//3.3 初始化AVFrame
AVFrame *pFrame = av_frame_alloc();


//3.4 获取到编码文件的参数信息
//声道
int64_t in_channel_layout = av_get_default_channel_layout(pCodecContext->channels);

//3.5 参数设置
au_convert_ctx = swr_alloc();
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt,
out_sample_rate,
in_channel_layout, pCodecContext->sample_fmt,
pCodecContext->sample_rate, 0, NULL);
swr_init(au_convert_ctx);

//4. 读取编码数据到AVPacket 然后将数据解码存储到AVFrame 转换存储数据
//4.1 读取编码数据到AVPacket
int got_picture;
int index = 0;
FILE *outputFile = fopen(output, "wb");
while (av_read_frame(pFormatContext, packet) >= 0) {
if (packet->stream_index == audioStream) {
//4.2 将数据解码存储到AVFrame
if (avcodec_decode_audio4(pCodecContext, pFrame, &got_picture, packet) < 0) {
LOGE("解码失败");
return -1;
}

if (got_picture > 0) {
//4.3 转换音频数据
swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE,
(const uint8_t **) pFrame->data, pFrame->nb_samples);
LOGE("index:%5d\t pts:%lld\t packet size:%d\n",index,packet->pts,packet->size);

//4.4 存储数据
fwrite(out_buffer, 1, static_cast<size_t>(out_buffer_size), outputFile);
index++;
}
}
//5. 释放相关资源
av_packet_unref(packet);
}

swr_free(&au_convert_ctx);
fclose(outputFile);
av_free(out_buffer);
// Close the codec
avcodec_close(pCodecContext);
// Close the video file
avformat_close_input(&pFormatContext);

return 0;
}


extern "C"
JNIEXPORT jint JNICALL
Java_zzw_com_ffmpegdemo_VideoUtils_audio_1decode(JNIEnv *env, jclass type, jstring input_,
jstring output_) {
const char *input = env->GetStringUTFChars(input_, 0);
const char *output = env->GetStringUTFChars(output_, 0);
int flog = audio_decode_example(input, output);

env->ReleaseStringUTFChars(input_, input);
env->ReleaseStringUTFChars(output_, output);

return flog;
}

参考地址:
https://blog.csdn.net/leixiaohua1020/article/details/46890259

ffmpeg android视频解码

发表于 2018-05-10 | 分类于 ffmpeg |
字数统计: 1,094字 | 阅读时长 ≈ 6分钟

解码流程:

  1. 获取文件信息,数据存储在AVFormatContext里面
  2. 根据AVFormatContext获取对应的AVCodecContext
  3. 解码原始数据AVPacket,解码为自己需要的数据AVFrame
  4. 释放相关资源


(图片来源于网络)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include "lang.h"
#include <string>
//封装格式
//解码
#include "log.h"

extern "C" {
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

};


static void video_decode_example(const char *outfilename, const char *filename) {
//1.注册
av_register_all();

AVFormatContext *pFormatCtx = NULL;
//2. 打开文件
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) {
LOGE ("打开文件失败");
return;
}
//3. 获取流信息,数据封装在AVFormatContext里面
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
LOGE ("获取流信息失败");
return;
}
//只输出输入文件的格式信息
av_dump_format(pFormatCtx, 0, filename, 0);
int video_index = -1;
//4. 从流中遍历获取video的index
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
video_index = i;
LOGE ("video_index = %d", video_index);
break;
}
}
if (video_index == -1) {
LOGE ("遍历获取video_index失败");
return;
}
AVCodecContext *pCodecCtxOrg = NULL;
AVCodecContext *pCodecCtx = NULL;

AVCodec *pCodec = NULL;
//5. 解码器获取
//5.1 根据video_index获取解码器上下文AVCodecContext
pCodecCtxOrg = pFormatCtx->streams[video_index]->codec; // codec context
//5.1 根据AVCodecContext获取解码器
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);

if (!pCodec) {
LOGE ("解码器获取失败");
return;
}

//6.获取一个AVCodecContext实例,并将第五步获取的AVCodecContext数据copy过来,解码的时候需要用这个
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_copy_context(pCodecCtx, pCodecCtxOrg) != 0) {
LOGE ("解码器上下文数据copy失败");
return;
}
//7. 打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
LOGE ("打开解码器失败");
return;
}
//原始数据帧
AVFrame *pFrame = NULL;
//yuv数据帧
AVFrame *pFrameYUV = NULL;
//内存开辟 不要忘记free
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
int numBytes = 0;
uint8_t *buffer = NULL;
//根据需要解码的类型,获取需要的buffer,不要忘记free
numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffer, AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height, 1);

//获取SwsContext
struct SwsContext *sws_ctx = NULL;
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC,
NULL, NULL, NULL);

FILE *pFile = fopen(outfilename, "wb+");
int ret;
AVPacket packet;
int frameFinished = 0;
//8. 根据AVFormatContext 读取帧数据,读取的编码数据存储到AVPacket里面
while (av_read_frame(pFormatCtx, &packet) >= 0) {
if (packet.stream_index == video_index) {
//9. 将读取到的AVPacket,转换为AVFrame
ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (ret < 0) {
LOGE("解码失败");
return;
}
if (frameFinished) {
//10. 将原始的AVFrame数据转换为自己需要的YUV AVFrame数据
sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//11. 根据YUV AVFrame数据保存文件
if (pFile == NULL)
return;
int y_size = pCodecCtx->width * pCodecCtx->height;

//yuv420 存储为4:1:1
fwrite(pFrame->data[0], 1, static_cast<size_t>(y_size), pFile); //y
fwrite(pFrame->data[1], 1, static_cast<size_t>(y_size / 4), pFile);//u
fwrite(pFrame->data[2], 1, static_cast<size_t>(y_size / 4), pFile);//v
}
}
av_packet_unref(&packet);
}

//flush decoder
//FIX: Flush Frames remained in Codec
//12. 刷新解码器
while (1) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (ret < 0)
break;
if (!frameFinished)
break;
sws_scale(sws_ctx, (const unsigned char *const *) pFrame->data, pFrame->linesize, 0,
pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);

int y_size = pCodecCtx->width * pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, static_cast<size_t>(y_size), pFile); //Y
fwrite(pFrameYUV->data[1], 1, static_cast<size_t>(y_size / 4), pFile); //U
fwrite(pFrameYUV->data[2], 1, static_cast<size_t>(y_size / 4), pFile); //V
LOGE("Flush Decoder: Succeed to decode 1 frame!\n");
}
//release resource
sws_freeContext(sws_ctx);
fclose(pFile);
av_free(buffer);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrg);
avformat_close_input(&pFormatCtx);

}


extern "C"
JNIEXPORT jint JNICALL
Java_zzw_com_ffmpegdemo_VideoUtils_decode(JNIEnv *env, jclass type, jstring input_,
jstring output_) {
const char *input_file_name = env->GetStringUTFChars(input_, 0);
const char *output_file_name = env->GetStringUTFChars(output_, 0);

video_decode_example(output_file_name, input_file_name);

env->ReleaseStringUTFChars(input_, input_file_name);
env->ReleaseStringUTFChars(output_, output_file_name);
return 0;

}

总结:

要解码,我们需要获取解码器AVCodec,解码器我们需要通过codec_id获取,codec_id我们需要通过AVStream获取,AVStream我们需要通过AVCodecContext获取,AVCodecContext我们要根据AVFormatContext获取,解码的时候我们要通过AVFormatContext读取,解码数据存储在AVFrame里面,编码数据存储在AVPacket里面。

参考:
https://blog.csdn.net/leixiaohua1020/article/details/46889389
https://blog.csdn.net/u011913612/article/details/53419986

ffmpeg编译

发表于 2018-05-06 | 分类于 ffmpeg |
字数统计: 1,610字 | 阅读时长 ≈ 9分钟

环境

  1. 云主机 Centos 7 x86_64 bbr系统
  2. ffmpeg-3.0.11库
  3. android-ndk-r13b
  1. ndk安装
    1
    2
    3
    4
    //下载
    wget https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip
    //解压
    unzip android-ndk-r13b-linux-x86_64.zip

ndk个版本下载地址选择:https://blog.csdn.net/shuzfan/article/details/52690554

2.配置环境变量并为ndk授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//进入当前用户根目录
cd ~
//编辑
vim ~/.bashrc

//编辑 ndk为例
export NDKROOT=/usr/ndk/android-ndk-r13b
export PATH=$NDKROOT:$PATH


//更新环境变量
source ~/.bashrc

//授权
chmod 777 -R /usr/ndk

3.下载ffmpge,解压授权

1
2
3
4
5
6
//下载
wget http://ffmpeg.org/releases/ffmpeg-3.0.11.tar.gz
//解压
tar zxvf ffmpeg-3.0.11.tar.gz
//授权
chmod 777 -R /usr/zzw/ffmpeg-3.0.11

ffmpeg各版本下载地址:http://ffmpeg.org/download.html

  1. 根目录创建ffmpegtemp文件夹,配置脚本build_android.sh,config我是windows+xftp的方式
    4.1 在windows上新建build_android.sh,并书写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    #!/bin/bash
    export TMPDIR=/usr/zzw/ffmpeg-3.0.11/ffmpegtemp
    NDK=/usr/ndk/android-ndk-r13b
    PLATFORM=$NDK/platforms/android-14/arch-arm/
    TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64

    function build_one
    {
    ./configure \
    --prefix=$PREFIX \
    --target-os=linux \
    --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
    --arch=arm \
    --sysroot=$PLATFORM \
    --extra-cflags="-I$PLATFORM/usr/include" \
    --cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \
    --nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \
    --enable-shared \
    --enable-runtime-cpudetect \
    --enable-gpl \
    --enable-small \
    --enable-cross-compile \
    --disable-debug \
    --disable-static \
    --disable-doc \
    --disable-asm \
    --disable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --disable-postproc \
    --disable-avdevice \
    --disable-symver \
    --disable-stripping \


    $ADDITIONAL_CONFIGURE_FLAG
    sed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.h
    sed -i '' 's/HAVE_LRINTF 0/HAVE_LRINTF 1/g' config.h
    sed -i '' 's/HAVE_ROUND 0/HAVE_ROUND 1/g' config.h
    sed -i '' 's/HAVE_ROUNDF 0/HAVE_ROUNDF 1/g' config.h
    sed -i '' 's/HAVE_TRUNC 0/HAVE_TRUNC 1/g' config.h
    sed -i '' 's/HAVE_TRUNCF 0/HAVE_TRUNCF 1/g' config.h
    sed -i '' 's/HAVE_CBRT 0/HAVE_CBRT 1/g' config.h
    sed -i '' 's/HAVE_RINT 0/HAVE_RINT 1/g' config.h

    make clean
    make -j4
    make install
    }

    # arm v7vfp
    CPU=armv7-a
    OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
    PREFIX=./android/$CPU-vfp
    ADDITIONAL_CONFIGURE_FLAG=
    build_one

    # CPU=armv
    # PREFIX=$(pwd)/android/$CPU
    # ADDI_CFLAGS="-marm"
    # build_one

    #arm v6
    #CPU=armv6
    #OPTIMIZE_CFLAGS="-marm -march=$CPU"
    #PREFIX=./android/$CPU
    #ADDITIONAL_CONFIGURE_FLAG=
    #build_one

    #arm v7vfpv3
    # CPU=armv7-a
    # OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfpv3-d16 -marm -march=$CPU "
    # PREFIX=./android/$CPU
    # ADDITIONAL_CONFIGURE_FLAG=
    # build_one

    #arm v7n
    #CPU=armv7-a
    #OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=$CPU -mtune=cortex-a8"
    #PREFIX=./android/$CPU
    #ADDITIONAL_CONFIGURE_FLAG=--enable-neon
    #build_one

    #arm v6+vfp
    #CPU=armv6
    #OPTIMIZE_CFLAGS="-DCMP_HAVE_VFP -mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
    #PREFIX=./android/${CPU}_vfp
    #ADDITIONAL_CONFIGURE_FLAG=
    #build_one

注意”\“后面不要放空格

4.2 使用xftp传输到云主机的ffmpeg解压根目录,并将该文件改为linux的编码

1
2
3
4
5
vim build_android.sh

#vim下执行
:set ff=unix
:wq

4.3 修改configure

1
2
3
4
5
6
7
8
9
10
11
12
13
#这段脚本的作用是使生成的静态库为.so为后缀,生成的库是这种格式:libavcodec-57.so ... 
#不加的话生成的库是这种格式:libavcodec.so.57 ...等等

#注释以下内容
#SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
#LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
#SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
#SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
#增加以下内容
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
  1. 执行脚本
    1
    ./build_android.sh

6.上面生成的是so动态库,生成.a静态库只需在build_android.sh修改如下:

1
2
--enable-static \
--disable-shared \

7.生成一个库,在第6步生成静态库.a文件下,在build_android.sh的函数build_one添加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/bin/bash
export TMPDIR=/usr/zzw/ffmpeg-3.0.11/ffmpegtemp
NDK=/usr/ndk/android-ndk-r13b
PLATFORM=$NDK/platforms/android-14/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64

function build_one
{
./configure \
--prefix=$PREFIX \
--target-os=linux \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--arch=arm \
--sysroot=$PLATFORM \
--extra-cflags="-I$PLATFORM/usr/include" \
--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \
--nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \
--enable-static \
--enable-runtime-cpudetect \
--enable-gpl \
--enable-small \
--enable-cross-compile \
--disable-debug \
--disable-shared \
--disable-doc \
--disable-asm \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-postproc \
--disable-avdevice \
--disable-symver \
--disable-stripping \


$ADDITIONAL_CONFIGURE_FLAG
sed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.h
sed -i '' 's/HAVE_LRINTF 0/HAVE_LRINTF 1/g' config.h
sed -i '' 's/HAVE_ROUND 0/HAVE_ROUND 1/g' config.h
sed -i '' 's/HAVE_ROUNDF 0/HAVE_ROUNDF 1/g' config.h
sed -i '' 's/HAVE_TRUNC 0/HAVE_TRUNC 1/g' config.h
sed -i '' 's/HAVE_TRUNCF 0/HAVE_TRUNCF 1/g' config.h
sed -i '' 's/HAVE_CBRT 0/HAVE_CBRT 1/g' config.h
sed -i '' 's/HAVE_RINT 0/HAVE_RINT 1/g' config.h

make clean
make -j4
make install

# 增加的内容
$TOOLCHAIN/bin/arm-linux-androideabi-ld \
-rpath-link=$PLATFORM/usr/lib \
-L$PLATFORM/usr/lib \
-L$PREFIX/lib \
-soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o \
$PREFIX/libffmpeg.so \
libavcodec/libavcodec.a \
libavfilter/libavfilter.a \
libswresample/libswresample.a \
libavformat/libavformat.a \
libavutil/libavutil.a \
libswscale/libswscale.a \
-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker \
$TOOLCHAIN/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a \

}

# arm v7vfp
CPU=armv7-a
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
PREFIX=./android/$CPU-vfp
ADDITIONAL_CONFIGURE_FLAG=
build_one

# CPU=armv
# PREFIX=$(pwd)/android/$CPU
# ADDI_CFLAGS="-marm"
# build_one

#arm v6
#CPU=armv6
#OPTIMIZE_CFLAGS="-marm -march=$CPU"
#PREFIX=./android/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one

#arm v7vfpv3
# CPU=armv7-a
# OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfpv3-d16 -marm -march=$CPU "
# PREFIX=./android/$CPU
# ADDITIONAL_CONFIGURE_FLAG=
# build_one

#arm v7n
#CPU=armv7-a
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=$CPU -mtune=cortex-a8"
#PREFIX=./android/$CPU
#ADDITIONAL_CONFIGURE_FLAG=--enable-neon
#build_one

#arm v6+vfp
#CPU=armv6
#OPTIMIZE_CFLAGS="-DCMP_HAVE_VFP -mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
#PREFIX=./android/${CPU}_vfp
#ADDITIONAL_CONFIGURE_FLAG=
#build_one

8.具体要编译哪个库,自己设置。 通过

1
./configure --help

命令查看怎么设置,通过打开configure文件查看所对应的库之间的依赖等。

依赖关系

1
2
3
4
5
6
7
8
9
10
11
# libraries, in linking order
avcodec_deps="avutil"
avcodec_select="null_bsf"
avdevice_deps="avformat avcodec avutil"
avfilter_deps="avutil"
avformat_deps="avcodec avutil"
avformat_suggest="network"
avresample_deps="avutil"
postproc_deps="avutil gpl"
swresample_deps="avutil"
swscale_deps="avutil"

通过这个就可以知道在android里面的so库加载顺序

1
2
3
4
5
6
7
8
9
10
static {
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("avfilter-6");
//自己实现逻辑编译的库
System.loadLibrary("native-lib");
}

也可以通过查看生成的lib/pkgconfig里面对应的pc文件查看具体的依赖关系

比如libavfilter.pc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
prefix=./android/armv7-a-vfp
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include

Name: libavfilter
Description: FFmpeg audio/video filtering library
Version: 6.31.100
Requires:
Requires.private: libswscale >= 4.0.100, libavformat >= 57.25.100, libavcodec >= 57.24.102, libswresample >= 2.0.101, libavutil >= 55.17.103
Conflicts:
Libs: -L${libdir} -lavfilter
Libs.private: -lm -lz -pthread
Cflags: -I${includedir}

jni小细节操作

发表于 2018-04-16 | 分类于 jni |
字数统计: 1,403字 | 阅读时长 ≈ 7分钟
  1. 数组的细节处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #include <jni.h>
    #include <string>
    #include <stdlib.h>

    int compare(const jint *a, const jint *b) {
    return *a - *b;
    }


    extern "C"
    JNIEXPORT void JNICALL
    Java_com_zzw_jnidemo_ArrayUtils_sort(JNIEnv *env, jclass type, jintArray array_) {
    jint *intArray = env->GetIntArrayElements(array_, NULL);
    jsize length = env->GetArrayLength(array_);

    //排序
    //void *ptr 指向待排序的数组的指针
    //size_t count 数组的元素数目
    //size_t size 数组每个元素的字节大小
    // int (*comp)(const void *, const void *) 比较函数。若首个参数小于第二个,则返回负整数值,若首个参数大于第二个,
    // 则返回正整数值,若两参数相等,则返回零。
    // void qsort( void *ptr, size_t count, size_t size,int (*comp)(const void *, const void *) );

    qsort(intArray, static_cast<size_t>(length), sizeof(int),
    reinterpret_cast<int (*)(const void *, const void *)>(compare));

    //mode =
    //0 表示 既要同步数据给jArray,又要释放intArray
    //JNI_COMMIT : copy content, do not free buffer ,会同步数据给jArray,但是不会释放intArray
    //JNI_ABORT : free buffer w/o copying back ,不会同步数据给jArray,但是会释放intArray
    env->ReleaseIntArrayElements(array_, intArray, JNI_ABORT);
    }

2.局部引用和全局引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//局部变量
extern "C"
JNIEXPORT void JNICALL
Java_com_zzw_jnidemo_ArrayUtils_localRef(JNIEnv *env, jclass type) {

//在Native层侯建的Java对象,不用了该怎么管理? 需要手动回收
//在Java层开辟的内存由谁管理(JavaGC管理),能开辟多大内存?

//字符串截取
jclass str_clz = env->FindClass("java/lang/String");
jmethodID init_str = env->GetMethodID(str_clz, "<init>", "()V");
jobject j_str = env->NewObject(str_clz, init_str);
//.....

//jobject不使用了,需要回收。删除了就不能使用了
env->DeleteLocalRef(j_str);
}



//全局变量,需要在合适的时机去释放 env->DeleteGlobalRef(globalStr);删除之后不能使用
jstring globalStr;
extern "C"
JNIEXPORT void JNICALL
Java_com_zzw_jnidemo_ArrayUtils_saveGlobalRef(JNIEnv *env, jclass type, jstring str_) {
globalStr = static_cast<jstring>(env->NewGlobalRef(str_));
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_zzw_jnidemo_ArrayUtils_getStrGlobalRef(JNIEnv *env, jclass type) {

return globalStr;
}

3.静态缓存策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//局部静态缓存
extern "C"
JNIEXPORT void JNICALL
Java_com_zzw_jnidemo_ArrayUtils_staticLcalCache(JNIEnv *env, jclass type, jstring name_) {
// jfieldID f_id =NULL;
static jfieldID f_id = NULL;//局部缓存 ,这个方法会被多次调用,不需要反复去获取jfieldID
if (!f_id) {
f_id = env->GetStaticFieldID(type, "name", "Ljava/lang/String;");
} else {
LOGE("f_id不为null");//加上static将会缓存
}
env->SetStaticObjectField(type, f_id, name_);
}

//全局静态缓存

static jfieldID jfieldID1;
static jfieldID jfieldID2;
static jfieldID jfieldID3;
//一般初始化的时候调用
extern "C"
JNIEXPORT void JNICALL
Java_com_zzw_jnidemo_ArrayUtils_initStatic(JNIEnv *env, jclass type) {
jfieldID1 = env->GetStaticFieldID(type, "name", "Ljava/lang/String;");
jfieldID2 = env->GetStaticFieldID(type, "name1", "Ljava/lang/String;");
jfieldID3 = env->GetStaticFieldID(type, "name2", "Ljava/lang/String;");
}

//如果反复调用设置值得函数,那么将不会每次都获取id
void setData(JNIEnv *env, jclass jclazz, jobject object) {
env->SetStaticObjectField(jclazz, jfieldID1, object);
env->SetStaticObjectField(jclazz, jfieldID2, object);
env->SetStaticObjectField(jclazz, jfieldID3, object);
}

4.异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

extern "C"
JNIEXPORT void JNICALL
Java_com_zzw_jnidemo_ArrayUtils_exception2(JNIEnv *env, jclass cls) {
jthrowable exc = NULL;
jmethodID mid = env->GetStaticMethodID(cls, "exceptionCallback", "()V");
if (mid != NULL) {
env->CallStaticVoidMethod(cls, mid);
}
LOGE("In C: Java_com_zzw_jnidemo_ArrayUtils_exception2-->called!!!!");
//这种也是可以检测的
// exc = env->ExceptionOccurred(); // 返回一个指向当前异常对象的引用
// if (exc) {

if (env->ExceptionCheck()) { // 检查JNI调用是否有引发异常
env->ExceptionDescribe();//打印错误信息
env->ExceptionClear(); // 清除引发的异常,在Java层不会打印异常的堆栈信息
env->ThrowNew(env->FindClass("java/lang/Exception"), "JNI抛出的异常!");
return;
}
//补救 。。。
mid = env->GetStaticMethodID(cls, "normalCallback", "()V");
if (mid != NULL) {
env->CallStaticVoidMethod(cls, mid);
}
}



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

try {
ArrayUtils.exception();
} catch (Exception e) {
e.printStackTrace();//打印抛出的异常
}
}

  1. c++调用java: 这里需要分为在主线程调用还是在子线程调用,在主线程调用就直接通过函数的env调用即可,在子线程调用则需要JavaVM ->AttachCurrentThread拿到env然后调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42


#include "JavaListener.h"
JavaVM *jvm;

JavaListener *javaListener;

pthread_t chidlThread;


void *childCallback(void *data)
{
JavaListener *javaListener1 = (JavaListener *) data;
//子线程调用
javaListener1->onError(0, 101, "c++ call java meid from child thread!");
pthread_exit(&chidlThread);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_zzw_jnithread_ThreadDemo_callbackFromC(JNIEnv *env, jobject instance) {

javaListener = new JavaListener(jvm, env, env->NewGlobalRef(instance));
//主线程调用
//javaListener->onError(1, 100, "c++ call java meid from main thread!");
//子线程调用
pthread_create(&chidlThread, NULL, childCallback, javaListener);

}



JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved)
{
JNIEnv *env;
jvm = vm;
if(vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
{
return -1;
}
return JNI_VERSION_1_6;
}

JavaListener.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

#include "jni.h"

#ifndef JNITHREAD_JAVALISTENER_H
#define JNITHREAD_JAVALISTENER_H


class JavaListener {

public:
JavaVM *jvm;
_JNIEnv *jenv;
jobject jobj;
jmethodID jmid;
public:
JavaListener(JavaVM *vm, _JNIEnv *env, jobject obj);
~JavaListener();

/**
* 1:主线程
* 0:子线程
* @param type
* @param code
* @param msg
*/
void onError(int type, int code, const char *msg);


};


#endif //JNITHREAD_JAVALISTENER_H

JavaListener.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#include "JavaListener.h"


void JavaListener::onError(int type, int code, const char *msg) {

if(type == 0)
{
JNIEnv *env;
jvm->AttachCurrentThread(&env, 0);
jstring jmsg = env->NewStringUTF(msg);
env->CallVoidMethod(jobj, jmid, code, jmsg);
env->DeleteLocalRef(jmsg);

jvm->DetachCurrentThread();


}
else if(type == 1)
{
jstring jmsg = jenv->NewStringUTF(msg);
jenv->CallVoidMethod(jobj, jmid, code, jmsg);
jenv->DeleteLocalRef(jmsg);
}
}

JavaListener::JavaListener(JavaVM *vm, _JNIEnv *env, jobject obj) {

jvm = vm;
jenv = env;
jobj = obj;

jclass clz = env->GetObjectClass(jobj);
if(!clz)
{
return;
}
jmid = env->GetMethodID(clz, "onError", "(ILjava/lang/String;)V");
if(!jmid)
return;


}

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ThreadDemo {

static
{
System.loadLibrary("native-lib");
}

private OnErrerListener onErrerListener;

public void setOnErrerListener(OnErrerListener onErrerListener) {
this.onErrerListener = onErrerListener;
}

public void onError(int code, String msg)
{
if(onErrerListener != null)
{
onErrerListener.onError(code, msg);
}
}

public interface OnErrerListener
{
void onError(int code, String msg);
}

public native void callbackFromC();

}

jni基础

发表于 2018-04-16 | 分类于 jni |
字数统计: 840字 | 阅读时长 ≈ 4分钟

JNI 的一般开发流程

1 定义好本地的 native 方法
2 javah 命令生成 .h 头文件
3 拷贝 xxx.h、jni_md.h、jni.h 到 VS 的工程目录并添加依赖进来
4 实现我们头文件中的 native 方法
5 生成 dll 动态,java 引入 dll 动态库运行即可

生成的 .h 文件参数详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"// "" 引入自己工程的头文件 <> 引入系统的头文件
/* Header for class com_darren_ndk12_NdkSimple */
// 用来打一个标记,c在编译的时候会把头文件 copy 到你引入的地方,不管是重复引用还是相互引用都只会 copy 一次
#ifndef _Included_com_darren_ndk12_NdkSimple
#define _Included_com_darren_ndk12_NdkSimple
#ifdef __cplusplus // 相当于 if 语句 c++
// 不管是 c 还是 c++ 统一都是采用 c 的编译方式,因为在c里面是不允许函数重载的,但是在 c++ 里面可以
extern "C" {
#endif
/*
* Class: com_darren_ndk12_NdkSimple
* Method: getSingnaturePassword
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

实现类详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实现我们的 native 方法
#include "com_darren_ndk12_NdkSimple.h"

// JNIEXPORT JNI 一个关键字,不能少(编译能通过),标记为该方法可以被外部调用
// jstring : 代表 java 中的 String
// JNICALL: 也是一个关键字,可以少的 jni call
// JNIEnv: 这个是 c 和 java 相互调用的桥梁,所有 function 搞清
// jobject: java传递下来的对象,就是本项目中 JniSimple java 对象
// jclass: 静态函数的时候java传递下来的 class 对象,就是本项目中的 JniSimple.class
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env,"940223");
}

JNIEnv是什么

在jni.h里面有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* JNI Native Method Interface.
*/

struct JNINativeInterface_;

struct JNIEnv_;


#ifdef __cplusplus //如果是c++就是JNIEnv_对象 JNIEnv_对象里面其实也是通过JNINativeInterface_调用
typedef JNIEnv_ JNIEnv;
#else //如果是c的话就是JNINativeInterface_指针
typedef const struct JNINativeInterface_ *JNIEnv;
#endif



struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
// ....
}

在实现的函数里面

1
2
3
4
5
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env,"940223");
}

所以为什么在c里面调用函数需要用(*env)-> ,而c++只需要用env->

JNIEnv流程

手写JNIEnv流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdlib.h>
#include <stdio.h>
// 定义一个结构体指针的别名
typedef const struct JNINativeInterface *JNIEnv;
// 模拟一个结构体
struct JNINativeInterface{
// 结构体的方法指针
char*(*NewStringUTF)(JNIEnv*,char*);
};

char* NewStringUTF(JNIEnv* env, char* c_str){
// c_str -> jstring
return c_str;
}

char* Java_com_darren_getSingnaturePassword(JNIEnv * env){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env, "940223");
}

void main(){

// 构建 JNIEnv* 对象
struct JNINativeInterface nativeInterface;
// 给结构方法指针进行复制(实现)
nativeInterface.NewStringUTF = NewStringUTF;

// 传给 Java_com_darren_ndk12_NdkSimple_getSingnaturePassword 的参数是 JNIEnv*
JNIEnv env = &nativeInterface;// 一级指针
JNIEnv* jniEnv = &env;// 二级指针

// 把 jniEnv 对象传给 Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
char* jstring = Java_com_darren_getSingnaturePassword(jniEnv);

// jstring 通过 JNIEnv 传给 java 层
printf("jstring = %s",jstring);

getchar();
}

c++基础语法

发表于 2018-04-15 | 分类于 c/c++ |
字数统计: 7,846字 | 阅读时长 ≈ 41分钟

c和c++联系

  1. c++代码可以混编c代码,既可以写c也可以调用c
  2. c++面向对象,c面向过程
  3. 开源框架大部分都是基于c++写的

打印

1
2
3
4
5
6
7
8
9
10
#include "stdio.h"
#include <iostream>

using namespace std;

void main() {
//打印
//cout << "hello world " << endl;//换行
cout << "hello world " ;//不换行
}

常量

1
2
3
4
5
6
7
8
9
void main() {
//常量
const int number = 0;
//在c文件,这里可以通过指针修改值,但是在c++中不能通过地址修改值,一般编译器编译不能通过,但是某一些编译器可以,但是也不能修改值
//int *number_p = &number;
//*number_p = 20;

getchar();
}

引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "stdio.h"
#include <iostream>

using namespace std;

//通过指针来变换
void swap(int *num1,int* num2) {
int temp = 0;
temp = *num1;
*num1 = *num2;
*num2 = temp;
}

//通过引用来变换
void swap(int &num1, int& num2) {
cout << "number1p = " << &num1 << " number2p = " << &num2 << endl;//number1p = 00BBFA08 number2p = 00BBF9FC
int temp = 0;
temp = num1;
num1 =num2;
num2 = temp;
}

void main() {
//引用:四驱模型值的拷贝,引用其实是地址赋值,可以看成同一块内存的另外一个变量

//交换值
int number1 = 10;
int number2 = 20;
//通过指针
//swap(&number1,&number2);
//通过引用
cout << " number1p = " << &number1 << " number2p = " << &number2 <<endl; // number1p = 00BBFA08 number2p = 00BBF9FC
swap(number1,number2);

cout << "number1 = " << number1 << " number2 = " << number2 << endl;// number1 = 20 number2 = 10


//加深理解
int a = 10;
int b = a; //赋值 b 和 a分别指向不同的内存
cout << "ap = " << &a << " bp = " << &b << endl;// ap = 010FF730 bp = 010FF724

int& c = a;//引用 a和c都指向一块地址
cout << "ap = " << &a << " cp = " << &c << endl;// ap = 010FF730 cp = 010FF730
c = 20;
cout << "a = " << a << " c = " << c << endl;//a = 20 c = 20

getchar();
}

常量引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "stdio.h"
#include <iostream>
#include <string.h>

using namespace std;

typedef struct
{
char name[20];
int age;
} Student;

//常量引用 const Student & stu只能读,不能修改
void insertStudent(/* const */ Student & stu) {
//stu.name = "李四"; //不能修改
strcpy_s(stu.name, "李四"); //这样的话就修改了值,如果不想修改stu,就在参数加上const 变为只读
}

void main() {
Student stu = {"张三" ,45};
insertStudent(stu);
cout << "stu.name = " << stu.name << " stu.age = " << stu.age << endl;
getchar();
}

重载 :c不支持重载 ,c++支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "stdio.h"
#include <iostream>
#include <string.h>

using namespace std;

//重载
//int add(int number1, int number2) {
//
// return number1 + number2;
//}

int add(int number1, int number2,bool cache = false) {

cout << cache << endl;

return number1 + number2;
}
int add(int number1, int number2,int number3) {

return number1 + number2 + number3;
}


void main() {
int number1 = 10;
int number2 = 20;
int number3 = 30;
int sum1 = add(number1, number2);
int sum2 = add(number1, number2 , number3);

printf("%d , %d", sum1, sum2);

getchar();
}

类 JAVA里的Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "stdio.h"
#include <iostream>
#include <string.h>

using namespace std;

class Student{
private: // 私有 包装,影响下面所有的属性或者方法
char * name;
int age;

public://影响下面所有的方法或者属性

void setAge(int age){
this->age = age;
}
void setName(char*name) {
this->name = name;
}

int getAge() {
return this->age;
}

char * getName() {
return this->name;
}
};


void main() {
Student stu;
stu.setAge(25);
cout << stu.getAge() << endl;

Student *student = new Student();

student->setAge(24);

cout << student->getAge() << endl;

getchar();
}

注意:在开发过程中,cpp或者c会被编译为dll或者so供其调用者使用,一般把public的函数定义到h文件,不然调用者都不知道有哪些函数。

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Student{

public:
//1.
Student() {};

//2.
Student(char * name):age(0) { // 相当于thix->age = 0
this->name = name;
};

//3.
//构造函数相互调用 先调用两个参数的,在调用一个参数的
Student(char*name) :Student(name,0) { // 相当于thix->age = 0
};

//4.
Student(char*name,int age) {
this->name = name;
this->age = age;
};

private:
char * name;
int age;

public:

void setAge(int age){
this->age = age;
}
void setName(char*name) {
this->name = name;
}

int getAge() {
return this->age;
}

char * getName() {
return this->name;
}
};

析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

class Student{

public:
Student() {};

Student(char*name,int age) {
this->name = (char*)malloc(sizeof(char)*100);
strcpy(this->name,name);
this->age = age;
};

//析构函数:对象被回收的时候会被调用,只能有一个,不能有参数
~Student() {
//释放内存 这里name使用malloc举个例子
free(this->name);
}


private:
char * name;
int age;

public:

void setAge(int age){
this->age = age;
}
void setName(char*name) {
this->name = name;
}

int getAge() {
return this->age;
}

char * getName() {
return this->name;
}
};

malloc free new delete区别

  1. malloc/free一起用 new/delete一起用
  2. malloc/free不会调用构造函数和析构函数,new/delete会调用构造函数和析构函数
  3. 如果用了new,一定要记得delete释放内存

拷贝构造函数

1
2
3
4
Class(const Class& C)  
{
//..赋值
}

https://blog.csdn.net/lwbeyond/article/details/6202256

可变参数 java的Object…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#include "stdio.h"
#include <stdarg.h>
#include <iostream>
using namespace std;

//可变参数
int sum(int count,...) {
va_list vp;
//可变参数指定开始 count代表从哪里开始
va_start(vp,count);
int sum = 0;
for (int i = 0; i < count; i++) {
sum += va_arg(vp, int);
}
va_end(vp);
return sum;
}

int main()
{

cout<<sum(3, 2, 3, 4)<<endl;
getchar();
return 0;
}

static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "stdio.h"
#include <stdarg.h>
#include <iostream>
using namespace std;

class Student
{
public:
char * name;
int age;
static int tag;


Student() {
tag = 10;
}

static void change() {
tag += 20;
}

void change2() {
tag += 20;
}

};


//静态属性 在c++中必须初始化,初始化必须这么写
int Student::tag =15;

int main()
{
cout << Student::tag << endl;//15

Student stu;
cout << Student::tag << endl;//10

Student::change(); //静态函数调用静态变量
cout << Student::tag << endl;//30

stu.change2(); //非静态函数调用静态变量
cout << Student::tag << endl;//50

getchar();
return 0;
}
// 1. 调用使用 双冒号 ::
// 2. 静态属性 必须初始化
// 2. 静态函数只能操作静态的相关函数和属性

const函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

#include "stdio.h"
#include <stdarg.h>
#include <iostream>
using namespace std;

class C
{
public:
int age;
public:
const void change() {
this->age += 12;
}

//const 在()之后主要用来限制this关键字
void change2() const {
//this->age += 12; //不能对类的属性进行修改
}

C(int age) {
this->age = age;
}

};


int main()
{
C *c = new C(15);
c->change();
cout << c->age << endl;


getchar();
return 0;
}

友元函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "stdio.h"
#include <stdarg.h>
#include <iostream>
using namespace std;

class Person
{
public:
Person(int age) {
this->age = age;
};

int getAge() {
return this->age;
}
//友元函数 申明之后,friend_change就可以调用私有属性
friend void friend_change(Person * person, int age);


private:
int age = 0;


};

void friend_change(Person * person, int age) {
//age private修饰,在类的内部才能访问私有函数。如果非要访问,就得用到友元函数
//如果该方法设置为友元函数,那么在外部可以访问其私有属性
person->age = age;
}





int main()
{
Person person = Person(24);
friend_change(&person,20);
cout << person.getAge() << endl;//20
getchar();
return 0;
}

构造函数,析构函数,拷贝构造函数,普通函数,静态函数,友元函数实现

Student.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "stdafx.h"

class Student
{
private:
int age;
int time;

public:
//静态属性的申明
static int tag;

public:
//构造函数
Student();
Student(int age);
Student(int time, int age);

//析构函数
~Student();
//拷贝构造函数
Student(const Student &student );


public:
void setAge(int age);
void setTime(int time);

int getAge();
int getTime();

void print() const;
//静态函数
static void changeTag(int tag_replease);
//友元函数
friend void changeAge(Student* stu,int age);

};

实现类 Student.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "stdafx.h"
#include "Student.h"
#include <iostream>


//实现类,不一定都要全部实现


//静态属性的申明
int Student::tag = 0;


//构造函数
Student::Student(){

};

Student::Student(int age):time(200) { //time默认赋值
this->age = age;
}

Student::Student(int age,int time) {
this->age = age;
this->time = time;
}

//析构函数
Student::~Student() {
//资源回收
}
//拷贝构造函数
Student::Student(const Student& student) {

}

//普通方法
void Student::setAge(int age) {
this->age = age;
}
void Student::setTime(int time) {
this->time = time;
}

int Student::getAge() {
return this->age;
}
int Student::getTime() {
return this->time;
}

// const函数
void Student::print() const {
//this->age = 20;//不能操作this
std::cout << this->age << " " << this->time << " "<< tag << std::endl;
}

//静态函数
void Student::changeTag(int tag_replease) {
tag = tag_replease;
}

//友元函数 不需要加 Student::
void changeAge(Student* stu, int age) {
stu->age = age;
}

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stdafx.h"
#include "Student.h"


void main() {
Student* stu = new Student(24, 1000);
//调用const函数
stu->print();

//调用静态函数
Student::changeTag(36);
stu->print();

//调用友元函数
changeAge(stu, 37);
stu->print();


delete(stu);

getchar();
}

友元类 :java的反射获取属性可以理解为就是有一个Class类的友元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ImageView {
public:
//申明Class是ImageView 的友元类
friend class Class;
private:
int age;
};

class Class{
public:
void changeAge(int age) {
//正常情况下这是不能直接用的,申明友元类就可以用了
aObj.age = age;
}

int getAge() {
//正常情况下这是不能直接用的,申明友元类就可以用了
return aObj.age;
}
private :
ImageView aObj;
};


void main() {
Class b;
b.changeAge(10);
std::cout << b.getAge() << std::endl; // 10
getchar();
}

操作运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

#include <iostream>

using namespace std;


class Vector
{
public:
Vector(int x, int y){
this->x = x;
this->y = y;
}

Vector(const Vector &vector){
this->x = vector.x;
this->y = vector.y;
cout << "拷贝构造函数" << endl;
}
private:
int x;
int y;

public:
void setX(int x){
this->x = x;
}
void setY(int y){
this->y = y;
}

int getX(){
return this->x;
}
int getY(){
return this->y;
}

// 重载减号运算符
// 为什么要用引用,为了防止重复创建对象
// const 关键常量,为了防止去修改值
Vector operator - (const Vector &vector){
int x = this->x - vector.x;
int y = this->y - vector.y;
Vector res(x, y);
return res;// 不建议返回引用
}

// 自增减运算符
void operator ++ (){// ++X
this->x = this->x++;
this->y = this->y++;
}

void operator ++ (int){// X++
this->x = this->x++;
this->y = this->y++;
}

// 自减
// 输出运算符
friend ostream & operator << (ostream &_Ostr, const Vector &vector){
_Ostr << vector.x << "," << vector.y << endl;
return _Ostr;
}

// 条件运算符
bool operator == (const Vector &vector){
return (this->x == vector.x && this->y == vector.y);
}
};

// 定义在类的外面,一般来讲我们定义在类的里面
// 重载运算 + :operator +
Vector operator + (Vector vector1, const Vector vector2){
int x = vector1.getX() + vector2.getX();
int y = vector1.getY() + vector2.getY();
Vector vector(x,y);
return vector;
}

void main(){
Vector vector1(2, 3);
Vector vector2(2, 3);

// java 中 string + string

// char* str = "123" + "456";

// 重载运算符 +
// Vector vector = vector1 - vector2;

// Vector vector(1, 2);
// vector++;
// ++vector;

// cout << vector.getX() << " , " << vector.getY() << endl;
// cout << vector << vector;
bool isEqual = vector1 == vector2;
cout << isEqual << endl;

// 可以重载加其他对象 Person

getchar();
}



// 括号运算符
class Array
{
public:
Array(int size){
this->size = size;
this->array = (int*)malloc(sizeof(int)*size);
}
~Array(){
if (this->array){
free(this->array);
this->array = NULL;
}
}

Array(const Array& array){
this->size = array.size;
this->array = (int*)malloc(sizeof(int)*array.size);

// 值的赋值
for (int i = 0; i < array.size; i++)
{
this -> array[i] = array.array[i];
}
}

private:
int size;
int* array;

public:
void set(int index,int value){
array[index] = value;
}

int get(int index){
return this->array[index];
}

int getSize(){
return this->size;
}

// 操作符[]
int operator[](int index){
return this->array[index];
}
};

void printfArray(Array array){
for (int i = 0; i < array.getSize(); i++)
{
cout << array[i] << endl;
}
}

void main(){

Array *array = new Array(5);

array->set(0,0);
array->set(1, 1);
array->set(2, 2);

printfArray(*array);

delete(array);
getchar();
}

类的继承 : 类继承,构造函数调用顺序:先父类 -> 再子类 ,析构函数调用顺序 : 先子类 -> 再父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

// 初始化属性
class Person{
// 变量修饰符
public:// 本类中使用
// protected :子类中能使用 (默认)
// public :公开,任何地方都可以
char* name;
int age;

public:
Person(char* name, int age){
this->name = name;
this->age = age;
cout << "Person 构造函数" << endl;
}

public:
void print(){
cout << this->name << " , " << this->age << endl;
}
};


class Student : public Person
{
private:
char* courseName;
public:
// : Person(name,age) 调用构造函数初始化父类的属性
// 不光可以给父类初始化属性,还可以给本类的属性进行初始化,用 , 隔开即可
Student(char* name, int age, char* courseName) :Person(name, age), courseName(courseName){// 调用父类构造函数
cout << "Student 构造函数" << endl;
}

void print(){
cout << "courseName: " << courseName << endl;
}
};

class Teacher: public Person
{
public:
Teacher(char*name,int age):Person(name,age){

}
};

void main(){
Student stu("张三",24,"语文");
stu.print();
getchar();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

class Person {
protected:
char* name;
int age;
public:
Person(char* name, int age){
this->name = name;
this->age = age;
}
};

class Course
{
private:// java String
string name;
public:
Course(string name){
this->name = name;
}

public:
string _name(){
return this->name;
}
};


class Student : public Person
{
private:
Course course;
public:
Student(char* name, int age, string courseName) : Person(name, age), course(courseName){ // 初始化父类的属性
}

void print(){
cout << name << "," << age << "," << course._name().c_str() << endl;
}
};

多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

class Person{
private:
char * name;

public:
Person(char * name){
this->name = name;
}

char* _name(){
return this->name;
}
};

class Child
{
int age;
public:
Child(int age){
this->age = age;
}

int _age(){
return this->age;
}
};

// 多继承,在 java 里面是不允许多继承 , c++ 是可以的,但是你也不能有二义性(歧义)
class Student : public Person, public Child // 多继承 , 并没有实现(接口)
{
public:
Student(char* name,int age):Person(name),Child(age){

}
};

虚继承:解决二义性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
public:
char* name;
};

class B : virtual public A{ // virtual 确保继承过来的相同属性或者函数,只存在一份拷贝

};

class C :virtual public A{

};

class D : public B ,public C
{

};

多态:c++里面分为动态多态(子父类 需要virtual修饰),静态多态(函数的重载),区别是编译过程确定性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Activity
{
public:
virtual void onCreate(){ // 支持多态,虚函数
cout << "Activity 中的 onCreate" << endl;
}
};

class MainActivity : public Activity
{
public:
void onCreate(){
cout << "MainActivity 中的 onCreate" << endl;
}
};

class WelcomeActivity : public Activity
{
public:
void onCreate(){
cout << "WelcomeActivity 中的 onCreate" << endl;
}
};

void startActivity(Activity* activity){
activity->onCreate();
}

void main(){
Activity *activity1 = new MainActivity();// 父类 = new 子类对象
Activity *activity2 = new WelcomeActivity();

// activity->onCreate();
// c++ 中的多态是怎样的,默认情况下不存在
// 父类指向子类的引用,重写 ,里氏替换原则
// 程序在编译期间并不知晓运行的状态(我需要运行那个函数),只要在真正运行的过程中才会去找需要运行的方法
startActivity(activity1);//如果Activity里面onCreate不加virtual,那么将会执行Activity的onCreate不会执行MainActivity的onCreate
startActivity(activity2);

// c++ 多态:动态多态(子父类),静态多态(函数的重载)(编译过程确定性的区别)

getchar();
}

抽象类,抽象函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// java 中类似的 抽象类,接口 纯虚函数
class BaseActivity // 跟 java 中的抽象类一个概念
{
public:
void onCreate(){// 普通函数
initView();
initData();
}

// 子类必须要实现
virtual void initData() = 0;// 虚函数,没有实现的,类似于 java 中的抽象方法,如果子类不实现会报错

virtual void initView() = 0;

};

// 如果不实现父类的纯虚函数,那么 MainActivity 也会变成抽象类,抽象类不允许实例化
class MainActivity : public BaseActivity
{
public:
void initData(){
cout << "initData" << endl;
}

void initView(){
cout << "initView" << endl;
}
};

void main(){

BaseActivity *m_a = new MainActivity();

m_a->onCreate();

getchar();
}

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class ClickListener{// 所有的函数都是虚函数,那么就可以认为是接口
public:
virtual void click() = 0;
};

class ImageClickListener : public ClickListener
{
public :
void click(){
cout << "图片点击" << endl;
}
};

void main(){
// 函数指针的时候:回调可以用 指针函数作为回调,纯虚函数类进行回调(接口)
// ClickListener *listener = new ImageClickListener();

//直接调用
// listener->click();

getchar();
}

模板函数 : java 中的泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 模板函数的定义
template <typename T> T add(T number1, T number2){
return number1 + number2;
}

void main(){

int sum1 = add(1,2);

cout << sum1 << endl;

int sum2 = add(1.0, 2.0);

cout << sum2 << endl;

int sum3 = add(1.0f, 2.0f);

cout << sum3 << endl;

getchar();
}

模板类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template <typename T> class Callback{
public:
void onError(){

}

void onSucceed(T result){
cout << result << endl;
}
};

// 模板类继承 ,子类如果也是模板类
// 如果子类不是模板类
class HttpCallback : public Callback<int>{

};

template <class T>
class HttpCallback : public Callback<T>{

};

void main(){
HttpCallback<int> *callback = new HttpCallback<int>();

callback->onSucceed(12);

getchar();
}

如果开发中涉及到模板类,申明和实现要写在同一个类里面: hpp = h + cpp/c (编译)

例如:c++实现ArrayList

ArrayList.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <malloc.h>
//------------------类的定义-------------------//

template<class E>
class ArrayList {
public:
// 数组头指针
E *array = NULL;
// 数组长度
int len = 0;
// 数据大小
int index = 0;
public:
ArrayList();

ArrayList(int len);

~ArrayList();

ArrayList(const ArrayList &list);

public:
bool add(E e);

int size();

E get(int index);

E remove(int index);

private:
void ensureCapacityInternal(int i);

void grow(int capacity);
};

//------------------类的实现-------------------//
template<class E>
ArrayList<E>::ArrayList() {

}

template<class E>
ArrayList<E>::ArrayList(int len) {
if (len == 0) {
return;
}
this->len = len;
this->array = (E *) malloc(sizeof(E) * len);
}

template<class E>
ArrayList<E>::~ArrayList() {
if (this->array) {
free(this->array);
this->array = NULL;
}
}

template<class E>
ArrayList<E>::ArrayList(const ArrayList &list) {
this->index = list.index;
this->len = list.len;
// 深拷贝
this->array = (E *) malloc(sizeof(E) * len);
memcpy(this->array,list.array,sizeof(E) * len);
}

template<class E>
E ArrayList<E>::get(int index) {
return this->array[index];
}

template<class E>
int ArrayList<E>::size() {
return this->index;
}

template<class E>
E ArrayList<E>::remove(int index) {
E old_value = this->array[index];
// 计算出需要逻动的个数
int numMoved = this->index - index - 1;

// 从前面不断的逻动
for (int i = 0; i < numMoved; ++i) {
array[index + i] = array[index + i + 1];
}

this->index -= 1;
return old_value;
}

template<class E>
bool ArrayList<E>::add(E e) {
ensureCapacityInternal(index + 1);
this->array[index++] = e;
return true;
}

// 是否需要调整当前数组大小
template<class E>
void ArrayList<E>::ensureCapacityInternal(int minCapacity) {
// 当前数组是不是空,或者 len 是不是 0
if (this->array == NULL) {
minCapacity = 10;// 第一次初始化大小
}

// 判断要不要扩容
if (minCapacity - len > 0) {
grow(minCapacity);
}
}

// 扩容创建新的数组
template<class E>
void ArrayList<E>::grow(int capacity) {
// 计算新数组大小的长度
int new_len = len + (len >> 1);

if (capacity - new_len > 0) {
new_len = capacity;
}

// 创建新的数组
E *new_arr = (E *) malloc(sizeof(E) * new_len);

if (this->array) {
// 拷贝数据
memcpy(new_arr, array, sizeof(E) * index);
// 释放原来的内存
free(this->array);
}

array = new_arr;
len = new_len;
}

使用的时候引入ArrayList.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "ArrayList.hpp"
#include <jni.h>
#include <string>
#include <android/log.h>

#include "ArrayList.hpp"

extern "C"
JNIEXPORT void
JNICALL
Java_com_zzw_demo_MainActivity_test(
JNIEnv *env,
jobject /* this */) {

ArrayList<int> *list = new ArrayList<int>();//

for (int i = 0; i < 100; ++i) {
list->add(i);
}

for (int i = 0; i < list->size(); ++i) {
__android_log_print(ANDROID_LOG_ERROR,"TAG","i = %d",list->get(i));
}

delete(list);
}

异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

class Exception
{
public:
string msg;

public:
Exception(string msg){
this->msg = msg;
}

public:
const char *what(){
return this->msg.c_str();
}

};

// 异常的处理
void main(){
// c++ 中有自己一套异常的体系,不要去强记
// 但是 c++ 可以抛任何数据类型 try{}catch(数据类型 变量名){}
// throw 抛异常

try{
int i = -1;

if (i == 0){
throw Exception("出异常了");
}

if (i< 0){
throw 12.5f;
}
}
catch (int number){
cout << "捕捉到异常" <<number << endl;
}
catch (Exception exception){
cout << "捕捉到异常:" << exception.what() << endl;
}
catch (...){
cout << "捕捉到其他异常:" << endl;
}

getchar();
}

字符串常见操作

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void main(){

string str1 = "123";
string str2("123");
string str3(5, 'A');// 5 个 A = AAAAA
string *str4 = new string("123");

// cout << str1.c_str() <<endl;
// cout << str2.c_str() << endl;
// cout << str3.c_str() << endl;
// cout << str4->c_str() << endl;

// string 与 char* 相互之间转换 c_str()
// const char* c_str1 = str1.c_str();
// cout << c_str1 << endl;

// char* -> string
char* c_str = "张三";
string str(c_str);// 对象
cout << str.c_str() << endl;


getchar();
}

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void main(){

string str("1234567");

// 1. 字符串的遍历
for (int i = 0; i < str.length(); i++)
{
cout << str[i] << endl;
}

// 迭代器遍历
for (string::iterator it = str.begin(); it < str.end(); it++)
{
cout << *it << endl;
}

try{
for (int i = 0; i < str.length()+2; i++)
{
cout << str.at(i) << endl;// 如果越界会抛异常
}

for (int i = 0; i < str.length()+2; i++)
{
cout << str[i] << endl;// 会导致程序宕机,AS里面是可以的
}
}
catch (...){
cout << "异常了" << endl;
}

getchar();
}

添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main(){

// 添加
string str1 = "123";
string str2 = "456";

// str1 = str1 + str2;
// str1.append(str2);


cout << str1.c_str() << endl;

getchar();
}

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main(){

// 删除
string str1 = "123 abc 123 abc 123";

// str1.erase(0,3);// 第一个参数:从哪里开始 ; 第二个参数:删除几个(默认值,字符串的结尾)

// 迭代器删除 2 bc 123 abc 123 解释 第一次删除第一个位置1,变为23 abc.... ,第二次删除第二个位置3,变为2 abc.... ,第三次删除第三个位置a,就是2 bc ....
for (string::iterator it = str1.begin(); it<str1.begin()+3; it++)// 删除一个字后都会从头开始计算
{
str1.erase(it);
}

cout << str1.c_str() << endl;

getchar();
}

替换

1
2
3
4
5
6
7
8
9
10
11
12
void main(){

string str1 = "123 abc 123 abc 123";
// 第一个参数:从哪里开始
// 第二个参数:替换几个
// 第三个参数:替换成谁
str1.replace(0,6,"1234");

cout << str1.c_str() << endl;

getchar();
}

查找

1
2
3
4
5
6
7
8
9
10
11
12
13

void main(){

string str1 = "123 abc 123 abc 123";
// 查找谁,从哪里开始
// int position = str1.find("123",0);
// 从后面往前面查
int position = str1.rfind("123");

cout << position << endl;

getchar();
}

大小写转换

1
2
3
4
5
6
7
8
9
10
11
12
void main(){

string str1 = "AAA abc BBB abc 123";
// 转换成大写
// transform(str1.begin(), str1.end(), str1.begin(), toupper);

transform(str1.begin(), str1.end(), str1.begin(), tolower);

cout << str1.c_str() << endl;

getchar();
}

STL标准模板库 : 容器+迭代+算法

  • 思想:集合,迭代器,算法 进行分离

vector容器(数组)

https://zh.cppreference.com/w/cpp/container/vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
容量

向量大小: vec.size();
向量真实大小: vec.capacity();
向量判空: vec.empty();


修改

末尾添加元素: vec.push_back();
末尾删除元素: vec.pop_back();
任意位置插入元素: vec.insert();
任意位置删除元素: vec.erase();
清空向量元素: vec.clear();


迭代器
开始指针:vec.begin();
末尾指针:vec.end(); //指向最后一个元素的下一个位置
指向常量的末尾指针: vec.cend();


元素的访问
下标访问: vec[1]; //并不会检查是否越界
at方法访问: vec.at(1); //以上两者的区别就是at会检查是否越界,是则抛出out of range异常
访问第一个元素: vec.front();
访问最后一个元素: vec.back();

stack容器(链表,数组)
https://zh.cppreference.com/w/cpp/container/stack

queue容器(数组,链表)
https://zh.cppreference.com/w/cpp/container/queue

list容器(链表)
https://zh.cppreference.com/w/cpp/container/list

set容器(红黑树,元素不重复)
https://zh.cppreference.com/w/cpp/container/set

multiset容器(元素可重复)
https://zh.cppreference.com/w/cpp/container/multiset

map容器(key不能重复)
https://zh.cppreference.com/w/cpp/container/map

multimap容器(以key分组)
https://zh.cppreference.com/w/cpp/container/multimap

对象添加到容器需要注意的:

  1. java 中把对象添加到了集合,c++ 中会调用对象的拷贝构造函数,存进去的是另一个对象
  2. 在c++中将对象加入到容器,需要有默认的构造函数
  3. 析构函数也可能回调用多次,如果说在析构函数中释放内存,需要在拷贝构造函数中进行深拷贝

仿函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

class Compare{
// 重载了括号运算符
public:
void operator()(){
cout << "仿函数" << endl;
}
};

void compare1(){
cout << "普通函数" << endl;
}

// 函数对象(仿函数) 一元谓词,二元谓词
void main(){
Compare compare;

// 跟函数非常类似
compare();//打印“仿函数”
// 普通函数调用
compare1();

getchar();
}

谓词

一元谓词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

// 一元谓词
void print(int number){
cout << number << endl;
}

// 仿函数 - 一元谓词 (能够记录状态)
class PrintObj
{
public:
int count = 0;
public:
void operator()(int number){
cout << number << endl;
count++;
}
};

// 回调函数和仿函数的区别
void main() {

set<int> set1;
set1.insert(1);
set1.insert(2);
set1.insert(3);
set1.insert(4);

// for_each 迭代器 ,非常重要的一点就是:仿函数如果要保存记录状态,要确保对象一致,可以用返回值
// for_each(set1.begin(),set1.end(),print);//会打印
PrintObj printObj;
//for_each(set1.begin(), set1.end(), printObj);
//cout << "个数:" << printObj.count << endl; //个数:0 ,要确保对象一致
printObj = for_each(set1.begin(), set1.end(), printObj);
cout << "个数:" << printObj.count << endl; //个数:4 ,对象一致

getchar();
}

二元谓词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#include <set>
#include <iostream>

using namespace std;

class CompareObj{
public:
int count = 0;
public:
bool operator()(const string str1, const string str2) {
return str1 > str2;
}
};

void main() {
// 二元谓词的仿函数
set<string, CompareObj> set1;
set1.insert("aaa");
set1.insert("aAa");
set1.insert("ccc");
set1.insert("ddd");
// 是否包含 aaa , 遍历比较 , 找方法
for (set<string>::iterator it = set1.begin(); it != set1.end(); it++)
{
cout << (*it).c_str() << endl;
}
//ddd ccc aaa aAa
getchar();
}

预定义函数对象(自定义重载 () 运算符),函数适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<iostream>
#include<vector>
#include<set>
#include<functional>
#include<algorithm>

using namespace std;


// 自定义重载 () 运算符
//预定义函数对象
void main(){
// c/c++ 提供了很多定义好的函数对象
// 常见的几个 less ,greater,plus,equal_to
plus<string> strAdd;
string str = strAdd("aaa","bbb");

// cout << str.c_str() << endl;

set<string, greater<string>> set1;
set1.insert("aaa");
set1.insert("bbb");
set1.insert("ccc");

// 判断是不是包含 aaa
// 怎么写仿函数,一定要确定好你的仿函数的参数
// bind2nd 函数适配器 , aaa 相当于 equal_to 中的 right
set<string, greater<string>>::iterator find_it = find_if(set1.begin(), set1.end(),bind2nd(equal_to<string>(),"aaa"));
if (find_it != set1.end()){
cout << "找到了" << (*find_it).c_str() << endl;
}
else
{
cout << "没有找到" << endl;
}

getchar();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include<vector>
#include<set>
#include<functional>
#include<algorithm>

using namespace std;

class Equal
{
private:
int equal_number;
public:
Equal(int equal_number) {
this->equal_number = equal_number;
}
public:
bool operator()(const int& number) {
return number == equal_number;
}
};

void main() {
vector<int> vector1;
vector1.push_back(1);
vector1.push_back(2);
vector1.push_back(3);
vector1.push_back(2);
vector1.push_back(4);
vector1.push_back(2);

// 找集合中 等于 2 的个数
int count = count_if(vector1.begin(), vector1.end(), Equal(2));
cout << "count = " << count << endl;

// 预定义好的函数对象 + 函数适配器
count = count_if(vector1.begin(), vector1.end(), bind2nd(equal_to<int>(), 2));
cout << "count = " << count << endl;

getchar();
}
  • foreach,transform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
#include<vector>
#include<set>
#include<functional>
#include<algorithm>

using namespace std;

void print(int number) {
cout << number << endl;
}

// 进行修改
int transform_print(int number) {
// cout << number << endl;
return number + 3;
}

// foreach,transform,find,find_if,count,count_if,megre,sort,random_shuffle,copy,replace
// 常用预定义算法 循环,增,删,改,
void main() {
vector<int> vector1;
vector1.push_back(1);
vector1.push_back(2);
vector1.push_back(3);
vector1.push_back(4);

//for_each循环
// for_each(vector1.begin(), vector1.end(),print); //1234
vector<int> vector2;
vector2.resize(vector1.size());

//变换和循环
transform(vector1.begin(), vector1.end(), vector2.begin(), transform_print);//4567

for_each(vector2.begin(), vector2.end(), print);

getchar();
}
  • find,find_if
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include<vector>
#include<set>
#include<functional>
#include<algorithm>

using namespace std;

void print(int number) {
cout << number << endl;
}

// 进行修改
int transform_print(int number) {
// cout << number << endl;
return number + 3;
}

// foreach,transform,find,find_if,count,count_if,megre,sort,random_shuffle,copy,replace
// 常用预定义算法 循环,增,删,改,
void main() {
vector<int> vector1;
vector1.push_back(1);
vector1.push_back(2);
vector1.push_back(3);
vector1.push_back(4);

vector<int>::iterator find_it = find(vector1.begin(), vector1.end(), 2);

if (find_it != vector1.end()){
cout << "包含" << endl;
}
else
{
cout << "不包含" << endl;
}

// 有没有大于2的,自定义函数对象,预定义函数对象+函数适配器,省略...
//findif() //上面有
getchar();
}
  • count,count_if
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

void main(){
vector<int> vector1;
vector1.push_back(1);
vector1.push_back(2);
vector1.push_back(3);
vector1.push_back(2);
vector1.push_back(4);

int number = count(vector1.begin(), vector1.end(), 2);

cout << "等于2的个数:" << number << endl;

number = count_if(vector1.begin(), vector1.end(), bind2nd(less<int>(), 2));

cout << "小于2的个数:" << number << endl;

number = count_if(vector1.begin(), vector1.end(), bind2nd(greater<int>(), 2));

cout << "大于2的个数:" << number << endl;

getchar();
}
  • megre: 两个有序数组进行合并 - 归并排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

using namespace std;


void print(int number) {
cout << number << endl;
}

// megre,sort,random_shuffle,copy,replace
void main() {

// 两个有序数组进行合并 - 归并排序
vector<int> vector1;
vector1.push_back(1);
vector1.push_back(2);
vector1.push_back(3);

vector<int> vector2;
vector1.push_back(4);
vector1.push_back(5);
vector1.push_back(6);

vector<int> vector3;
vector3.resize(6);
merge(vector1.begin(), vector1.end(), vector2.begin(), vector2.end(), vector3.begin());
for_each(vector3.begin(), vector3.end(), print);

getchar();
}
  • soft,random_shuffle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void print(int number) {
cout << number << endl;
}

// sort,random_shuffle
void main() {

vector<int> vector1;
vector1.push_back(1);
vector1.push_back(3);
vector1.push_back(2);
vector1.push_back(4);

sort(vector1.begin(), vector1.end(), less<int>());
for_each(vector1.begin(), vector1.end(), print);

cout << "循环结束" << endl;

// 打乱循序
random_shuffle(vector1.begin(), vector1.end());
for_each(vector1.begin(), vector1.end(), print);

getchar();
}
  • copy,replace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

void print(int number) {
cout << number << endl;
}

//copy,replace
void main() {

vector<int> vector1;
vector1.push_back(1);
vector1.push_back(2);
vector1.push_back(3);
vector1.push_back(4);

vector<int> vector2;
vector2.resize(2);
copy(vector1.begin(), vector1.begin() + 2, vector2.begin());
for_each(vector2.begin(), vector2.end(), print);

replace(vector1.begin(), vector1.end(), 2, 22);
for_each(vector1.begin(), vector1.end(), print);

getchar();
}

c语法进阶

发表于 2018-04-10 | 分类于 c/c++ |
字数统计: 2,487字 | 阅读时长 ≈ 12分钟

数组参数传递

我们先看个例子,main入口 ,同样的代码打印的信息不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37


#include "stdafx.h"
#include "stdio.h"



void print2(int* arr) {
int len = sizeof(arr) / sizeof(int);
printf("len =%d\n", len);//len = 1
int i = 0;
for (; i < len; i++) {
printf("%d\n", arr[i]);
}
}

void print(int arr[]) {
int len = sizeof(arr) / sizeof(int);
printf("len =%d\n", len);//len = 1
int i = 0;
for (; i < len; i++) {
printf("%d\n", arr[i]);
}
}

int main()
{
int arr[] = { 1,2,3,4,5 };
int len = sizeof(arr) / sizeof(int);
printf("len =%d\n", len);//len = 5
print(arr);
printf("===================\n");
print2(arr);
printf("===================\n");
getchar();
return 0;
}

从这demo我们可以看到,数组作为参数传递的时候为什么获取不到长度?

数组作为参数传递,会退化成为一个指针,传递的是首地址 (高效)

数据类型剖析

  1. 数据类型本质: 一块连续大小的内存空间
    demo1
    1
    2
    int a; //告诉c和c++编译器开辟一块连续大小的4字节的内存空间
    a =32;

demo2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
int arr[] = { 1,2,3,4,5 };//内存大小空间 4x5=20
printf("%d , %d,%d ,%d", arr,arr+1,&arr,&arr+1);// 17758788 ,17758792,17758788 ,17758808
getchar();
return 0;
}

```
2. 数据类型别名
```c
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */

/* "cardinal indices and sizes" */
typedef jint jsize;

  1. void* 代表任意的数据类型的指针

变量的本质

  1. 变量的本质:固定内存块大小(一块连续大小的内存空间)的别名,通过变量去操作一块内存上的数据
  2. 变量的三要素:内存大小,名称,作用域

内存四驱模型

  1. 程序运行流程

    1. 操作系统会将物理磁盘上的代码load到内存
    2. 加载到内存后会将c代码分为4个区
    3. 后系统找到main程序入口运行
  2. 四驱模型

    1. 栈区: 由编译器自动分配的,存放一些局部变量和函数,内存会自动进行回收。
    2. 堆区: 一般由开发者自己开辟的,内存需要手动释放 malloc–>free new–>delete等。
    3. 全局区(数据区): 静态的一些常量,字符串等。
    4. 程序代码区: 存放函数的二进制代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

struct Student{
int age;
};

typedef Student _Student;

void change(int number) {
number = 300;
printf("%p\n", &number);//00B3F928
}

void change(int* p_number) {
*p_number = 300;
printf("%p\n", p_number);//00B3F9FC
}
void chanegStudentAge(_Student student) {
student.age = 20;
printf("%p\n", &student);//0135F74C
}

void main() {
int a = 10;
a = 20;
change(a);//把a的值复制给了number
printf("a =%d\n",a); //a = 20

change(&a);//直接操作a的地址
printf("a =%d\n", a); //a = 300


_Student student = _Student();
student.age = 5;
printf("student =%d\n", &student); //student =20314144
chanegStudentAge(student);
printf("age = %d", student.age);
getchar();
}

不管是基本数据类型还是结构体,c都是值传递,和java不同的是,java基本数据类型是值传递,对象是引用传递。所以在c当中一般都是指针传递

change(int number)

changep(int* p_number)

栈的开口方向

1
2
3
4
5
int a=5;//先入栈
int buffer[]={1,2,3};
int b=7;

fprint("%p , %p",&a,&b);

如果地址a>b(debug),说明开口向下。
a<b(release)开口向上。
buffer数据类型的方向和栈的开口方向没关系,都是向上。&buffer[0]>&buffer[1]

指针强化

指针也是一种数据类型,虽然占用四个字节,但是有数据类型。

野指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Student{
int age;
};



void main() {
Student* student =(Student*)malloc(sizeof(Student));

//释放的时候记得判断为NULL
if (student != NULL) {
free(student);//这个时候student变为野指针
//我们不需要的话最好置为NULL
student = NULL;
}


//释放的时候记得判断为NULL,这个时候就不会去free,从而避免错误
if (student != NULL) {
free(student);
}
getchar();
}

NULL
NULL也是一个内存指针,指针指向00000000,只不过我们不能进行操作。

1
2

printf("NULL=%p", NULL); //NULL=00000000

字符串和 buffer 强化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

void main() {
char buffer[100] = {'1','2','3'};//后面3-99 都是默认值0

//长度
printf(" strlen(buffer) = %d\n", strlen(buffer));//strlen(buffer) = 3 根据\0结束计算
//内存
printf(" sizeof(buffer) = %d\n", sizeof(buffer));//sizeof(buffer) = 100
//内容
printf("%d %d %d \n",buffer[0], buffer[3], buffer[75]);//49 0 0

printf("-------------------------------\n");

char buffer1[] = { '1','2','3' };
//长度
printf(" strlen(buffer1) = %d\n", strlen(buffer1));//strlen(buffer1) = 15 根据\0结束计算
//内存
printf(" sizeof(buffer1) = %d\n", sizeof(buffer1));//sizeof(buffer1) = 3
//内容
printf("%d %d %d \n", buffer1[0], buffer1[3], buffer1[8]);//49 -52 -52

//char buffer2[2] = { '1','2','3' }; 这个直接就报错了 编译不通过

printf("-------------------------------\n");
char buffer3[100] = { '1','2','\0', '2' };//后面4-99 都是默认值0
//长度
printf(" strlen(buffer3) = %d\n", strlen(buffer3));//strlen(buffer3) = 2 根据\0结束计算
//内存
printf(" sizeof(buffer3) = %d\n", sizeof(buffer3));//sizeof(buffer3) = 100
//内容
printf("%d %d %d %d %d \n", buffer3[0], buffer3[1], buffer3[2], buffer3[3], buffer3[8]);//49 50 0 50 0


printf("-------------------------------\n");
char buffer4[100] = { '0' };//把数据初始化为0
//长度
printf(" strlen(buffer4) = %d\n", strlen(buffer4));//strlen(buffer4) = 1 根据\0结束计算
//内存
printf(" sizeof(buffer4) = %d\n", sizeof(buffer4));//sizeof(buffer4) = 100
//内容
printf("%d %d %d %d %d \n", buffer4[0], buffer4[1], buffer4[2], buffer4[3], buffer4[8]);//48 0 0 0 0

printf("-------------------------------\n");
char buffer5[100] ; //数据全部默认为-52
//长度
printf(" strlen(buffer5) = %d\n", strlen(buffer5));//strlen(buffer5) = 109 根据\0结束计算
//内存
printf(" sizeof(buffer5) = %d\n", sizeof(buffer5));//sizeof(buffer5) = 100
//内容
printf("%d %d %d %d %d \n", buffer5[0], buffer5[1], buffer5[2], buffer5[3], buffer5[8]);//-52 -52 -52 -52 -52

printf("-------------------------------\n");
//char *buffer6 = "hello"; //和下面一样的
char buffer6[] = "hello"; // 相当于"hello\0"

//char * ,char[],malloc区别:前两个都是在栈里开辟内存,字符串放在常量区,数组定义的是将常量区的数据copy过来到char[]里面(这样的话栈里也有了数据),char*是直接指针指向常量,malloc的方式是在堆里面开辟内存,存储数据到堆里
//长度
printf(" strlen(buffer6) = %d\n", strlen(buffer6));//strlen(buffer6) = 5 根据\0结束计算
//内存
printf(" sizeof(buffer6) = %d\n", sizeof(buffer6));//sizeof(buffer6) = 6
//内容
printf("%d %d %d %d %d \n", buffer6[0], buffer6[1], buffer6[2], buffer6[3], buffer6[8]);//104 101 108 108 -52

getchar();
}

char * ,char[],malloc区别:前两个都是在栈里开辟内存,字符串放在常量区,数组定义的是将常量区的数据copy过来到char[]里面(这样的话栈里也有了数据),char*是直接指针指向常量,malloc的方式是在堆里面开辟内存,存储数据到堆里。从而可以知道,字符串可以在任何区域开辟内存。
如图,栈中从上往下依次是char[],char*,malloc()

开发模型强化

  1. 确定参数,传递指针
  2. 一定要考略健壮性
  3. 异常错误,抛出说明
  4. 不要直接轻易的去改变调用者传递给你的指针,如需必要可以先考略临时的变量操作。可以加上const修饰参数强制不可修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct AndroidBitmapInfo{
int width;
int height;
int format;
};


int androidBitmapGetInfo(AndroidBitmapInfo * pAndroidBitmapInfo) {

//2. 健壮性考略
if (pAndroidBitmapInfo == NULL) {
printf("pAndroidBitmapInfo is null");
//3. 异常错误,抛出说明
return -1;
}

//4. 不要直接轻易的去改变调用者传递给你的指针
//pAndroidBitmapInfo = &AndroidBitmapInfo();

pAndroidBitmapInfo->height = 1920;
pAndroidBitmapInfo->width = 1080;
return 0;
}



void main() {
//1. 确定参数,传递指针
AndroidBitmapInfo androidBitmapInfo;
int ret =androidBitmapGetInfo(&androidBitmapInfo);
if (ret == 0) {
printf("w=%d,h=%d", androidBitmapInfo.width, androidBitmapInfo.height);
}

getchar();
}

结构体强化

  • 在c中=是赋值操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    typedef struct {
    char name[50];
    int age;
    }Student;

    void main() {
    Student stu1 = {"张三",35};
    Student stu2;
    stu2 = stu1;
    printf("%p , %p \n", &stu1, &stu2);//007CFD7C , 007CFD3C
    printf("%s , %d \n", stu2.name, stu2.age);//张三 , 35
    getchar();
    }
  • 在结构体里面套指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    #include "stdafx.h"
    #include "stdio.h"
    #include <string.h>
    #include <Windows.h>

    //结构体套指针,必须要对结构体指针进行初始化(赋值)
    //指针可以指向任何地址,但是它的大小永远是4字节

    typedef struct {
    char* name;// 定义了一个name指针
    int age;
    }Student;

    void main() {
    Student stu;
    // 1.
    //stu.name = "李四"; // 将指针指向常量区的"李四"

    //2.
    //往stu.name里面copy数据 "李四" 不能直接copy
    //strcpy(stu.name, "李四");

    //3.
    //首先把char* name 指向堆区开辟的内存 ,然后把常量区的数据"李四" copy到堆里面
    stu.name =(char*) malloc(100);
    strcpy(stu.name, "李四");

    stu.age = 35;
    printf("%s , %d \n", stu.name, stu.age);//李四 , 35
    getchar();
    }
  • 深copy和浅copy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43


    typedef struct {
    char *name;
    int age;
    }Student;



    void copyToP(Student* from, Student *to) {
    //浅copy
    *to = *from;

    //深copy
    to->name = (char*)malloc(100);//开辟内存
    strcpy(to->name, from->name);//copy值
    };


    void main() {
    char * name =(char*) malloc(100);
    Student stu = {name,25};
    strcpy(stu->name, "张三");
    stu.age = 35;
    printf("%s , %d \n", stu.name, stu.age);//张三 , 35

    Student stu2 = {"李四",15};
    copyToP(&stu2,&stu);
    printf("%s , %d \n", stu2.name, stu2.age);//张三 , 35

    if (stu.name) {
    free(stu.name);
    stu.name = NULL;
    }

    if (stu2.name) {//如果是浅copy会释放出错,因为stu.name和stu2.name指向一个位置 stu.name已经free了
    free(stu2.name);
    stu2.name = NULL;
    }


    getchar();
    }
  • 结构体的偏移量 :见c基础的结构体内存开辟以及内存计算

1…345…8
曾大稳丶

曾大稳丶

80 日志
11 分类
20 标签
© 2018 — 2019 曾大稳丶
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
访问人数 人 总访问量 次