前言
许久未碰C++的相关内容,已经有些被大脑的程序执行Lru算法了,导致近期在做NDK开发的时候,各种操作卡顿,决心还是回忆整理一下相关的基础知识。
开始扯犊子
涉及的知识点
- 基本数据类型
- 输入函数
- 数组的定义
- 控制流程
- 指针
- 指针运算
- 数组与指针
- 指针变量名
- 指针和函数参数
- 指针数组和数组指针
- 二级指针
- 函数指针
- 内存分配
- 字符串
基本数据类型
- C++ 常见的基本数据类型:
int short long float double char bool - sizeof 关键字不是函数,计算类型所占用的内存大小
int i = 1;
printf("%d \n", i);
printf("i 的地址 %#x \n", &i);
printf("i 的类型,所占用的内存空间大小 %d \n", sizeof(i));
long l = 200000;
printf("%ld, sizeof: %d \n", l, sizeof(l));
char c = 'b';
printf("%c, sizeof: %d \n", c, sizeof(c));
float f1 = 1.2;
printf("%f, sizeof: %d \ n", f1, sizeof(f1));
double f2 = 23.99f;
printf("f2 的内存:%d \n", sizeof(f2));
输入函数
scanf函数,在vs使用的时候会报错,解决方法:https://www.cnblogs.com/dmego/p/6065144.html
int n;
scanf("%d", &n);
数组的定义
C /C++ 语言的数组,在申明的时候就必须确定大小和基本类型。
- 静态声明
在定义的时候,需要给定明确的大小;
静态申明,分配的控件是在程序的内存的stack (栈)中
int a_array[10];
错误的声明:
//错误调用, 申明的时候就必须知道大小
int n = 9; //n 是运行时才知道是9
scanf("%d", &n); //这边是运行时才确定大小
int a_array[n]; //申请数组必须在编译时确定大小,需要用来分配内存空间
正确的声明:
cont int n = 10; //const 表示常量的关键字,会在内存静态区域分配空间给n存储,让它任何时候调用n,默认是10, 所以可以确定数值
int a_array[n];
定义数组,必须给数组分配空间的大小,且连续
看下内存的分配
int占四个字节,地址连续网上加4
0x00EFFA90 +0 ....
0x00EFFA94 +1 ....
0x00EFFA98 +2 ....
0x00EFFA9C +3 ....
0x00EFFAA0 +4 ....
0x00EFFAA4 +5 ....
0x00EFFAA8 +6 ....
0x00EFFAAC +7 ....
0x00EFFAB0 +8 ....
0x00EFFAB4 +9 ....
- 动态声明(malloc 分配内存函数)
//40M 4byte *1024 *1024 *10 = 40M, int是4byte
int * a_array;
a_array = (int *)malloc(sizeof(int) * 1024 * 1024 * 10);
int i;
printf("地址: %#x ", &a_array);
for (i = 0; i < 10; i++) {
a_array[i] = i;
}
- 动态声明和静态声明的区别
静态数组是存储在stack 栈 ,stack的大小是有限制的,?10M,2M,平台相关, stack会自动释放
动态申请数组堆里面, 系统不会自动释放
控制流程
for
if
switch
while
break
contiue
同和java一样,就不多讲了。
指针
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。而指针就是我们用来访问地址,已经地址所在存储的值的工具。
- 什么是指针?
指针: 指针变量存储的是 变量类型对应的变量的地址(内存位置的直接地址)。
声明:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
int * ip ip 存储的是 int型 的变量的地址
-
相关单目运算符:
& 取地址操作符。运用在内存中的对象上面,即变量与数组元素
*表示间接寻址或者引用运算符。
有关对运算符的优先级,可以查看下图:
指针定义声明的时候,容易误解的地方:
int a = 10;
//int *p = &a; //这边定义和下面两行的定义同等意思
int *p;
p = &a;
即:
(int *p = &a)等同于 int *p; p = &a;
- 撸代码:
int i = 10;
int *p;
p = &i;
printf("i value: %d\n", i);
printf("p 的地址:%#x \n", &p); //%#x : 表示16进制输出
printf("i 的地址:%#x \n", &i);
printf("修改i ");
printf("i value: %d\n", i);
printf("sizeof(*p) %d\n", sizeof(*p));
double f = 23.99f;
double * fp;
fp = &f;
printf("f 的内存:%d \n", sizeof(f));
// fp 是一个变量,指向double 类型的数据的变量
// fp 是存储的 地址 是int的,地址是占用4个字节的。32 位 4 * 8
printf("sizeof(fp) %d\n", sizeof(fp));
printf("f 的地址:%#x \n", &f);
printf("fpi 的地址:%#x \n", &fp);
指针运算
*p 代表是它所指的地址上面的值的运算
p++ 是p 指针变量++,结果就是:p指向它的下一个地址
- 撸代码:
int main() {
int a = 10;
//int *p = &a;
int *p;
p = &a;
*p = *p + 10;
printf("p: %d, a: %d\n", *p, a);
int y = 1 + *p;
printf("y: %d\n", y);
*p += 1;
printf("p: %d, a: %d\n", *p, a);
(*p)++; //值增加
*p++; //指向的地址位移4个字节,一个地址4个字节
printf("p: %d, a: %d, p address: %#x\n", *p, a, p);
//*(p++); == p++; *p;
//1+2 * 3
printf("p: %d, a: %d, p address: %#x\n", *p, a, p);
system("pause");
return 0;
}
输出结果:
数组与指针
通过数组下标所能完成的任何操作都可以通过指针来实现。
而用指针编写的程序比用数组下标编写的程序执行速度快,但是,指针写的程序会比较难理解一点。
直接撸代码:
int main() {
//数组名,就是数组的首地址
int a[5] = {1,2,3,4,5};
int *p;
p = a;
int i;
printf("%#x\n", a);
// a+5 ,表示a 移动到数组的第5个位置
for (i = 0; p < a + 5; p++)
{
*p = i;
i++;
}
p = p + 1;
p++;
printf("p: %d\n", *p);
/*a[i] == *(p+i)
&a[i] == a+i*/
//p + i == &a[i] == a+i ,这三个是等价的
system("pause");
return 0;
}
解释两个点
第一点:a+5, a[5],数组名,就是数组的首地址,a+5就是从a首地址开始,相当于移到第六项, 如下图:
第二个点:p + i == &a[i] == a+i, 因为p指针存放的是a的地址,a本身是数组的首地址,a[i]表示的是对应的第i项的地址,所以 p + i == &a[i] == a+i
运行输出:
指针和函数参数
指针在函数中的使用,举例子如下:
//形参的修改不能带来这个实产的修改, 将a b 的值copy 给了 形参d, e 该函数最终调用是没有变化的
void swap(int d, int e) {
printf("d address: %#x, e address: %#x \n", &d, &e);
int temp;
temp = d;
d = e;
e = temp;
}
// 形参的修改不能带来这个实产的修改
// 将a b 的地址copy 给了 形参,然后,将这个份copy 的地址进行了切换,而地址所指的值是没有变的
void swap2(int *a, int *b) {
printf("swap2 a address: %#x, b address: %#x \n", a, b);
int *temp;
temp = a; // a address 给了temp
a = b; // a 的地址变成了b的地址
b = temp; //b 的地址变成了a 的地址
printf("swap2 a address: %#x, b address: %#x \n", a, b);
}
// 该函数最后是在指针所指引的地址上面的值进行操作,并且地址上的值发生了改变
void swap3(int *a, int *b) {
int temp;
temp = *a;
*a = *b; // *a 代表是a所指的地址上面的值
*b = temp; // 将 b 所在地址的值变成了temp
}
int main() {
int a, b;
a = 10;
b = 5;
printf("a address: %#x, b address: %#x \n", &a, &b);
swap(a, b);//e = a; d = b; 复制了一份值
printf("a = %d, b = %d\n", a, b);
swap2(&a, &b);//e = a; d = b; 复制了一份地址
printf("a = %d, b = %d\n", a, b);
swap3(&a, &b);//e = a; d = b;
printf("a = %d, b = %d\n", a, b);
system("pause");
return 0;
}
运行输出:
指针数组和数组指针
首先,要区分指针数组和数组指针
指针数组是指:是一个数组,数组里存储的是指针类型的变量
如: int * p1[4];
数组指针: 是一个指针,这个指针指向的是一个数组的首地址
如:int (*p2)[4]
指针数组和数组指针的关系如下图:
- 指针数组的例子
#include "stdafx.h"
#include <string.h>
#include <stdlib.h>
void sort(char *name[], int n);
int main() {
char *name[] = { "hello", "dongNao", "Alvin", "world" };
int i, n = 4;
sort(name, n);
for (i = 0; i < n; i++)
{
printf("%s\n", name[i]);
}
// 数组指针
// p2 指向的是?指向的是一个数组,一个有5个元素的数组
char(*p2)[5];
//p2 是一个指针变量名
system("pause");
return 0;
}
// 参数,是char 类型的指针数组
//name[i]是一个指针
void sort(char *name[], int n) {
char *temp;
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n - 1 - i; j++) {
if (strcmp(name[j], name[j + 1]) > 0)
{
temp = name[j];
name[j] = name[j + 1];
name[j + 1] = temp;
}
}
}
}
输出结果:
二级指针
二级指针,存的内容是一个一级指针的地址
用个例子来解释:
int main() {
int a = 100;
int *p;
p = &a;
int **p2;
p2 = &p;
}
p 的值就是 a 这个变量的地址
*p 运算,得到的就是a 的值
*p2 运算,得到的就是p的值
**p2 运算,得到的就是 p的值的 *运算得到的值 a;
函数指针
在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
- 函数指针的定义
int Func(int x); /*声明一个函数*/
int (*p) (int x); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/
- 活生生的例子:
int plus(int a, int b) {
return a + b;
}
int minus(int a, int b) {
return a - b;
}
int plus2(int *a, int *b) {
return *a + *b;
}
int minus2(char * a, char *b) {
return 0;
}
int main()
{
int result;
int c = 3;
int d = 5;
int(*calc)(int a, int b);
int(*calc2)(void *a, void *);
int * p(int a, int b); //声明一个返回值是int指针的函数
//calc = plus;
calc = minus;
//result = calc(3, 5);
//calc2 = (int(*)(void *, void *))plus2;
calc2 = (int(*)(void *, void *))minus2;
result = calc2(&c, &d);
printf("minus: %#x\n", minus);
printf("result: %d\n", result);
system("pause");
return 0;
}
输出结果:
解释一下上面的程序
- void 类型的指针:类似:java object
- 指针变量都是4个字节。都是用十六进制表示。
void* ->> int * / char * /float* - 上面两个表达式完全不同
int(*calc2)(void *a, void ); 函数指针
int * p(int a, int b); 一个返回值是(int * )指针的函数 等同 (int) p(int a, int b)
内存分配
内存:
3区:程序区、静态存储区、动态存储区
程序区
程序的二进制文件(代码)
静态存储区
全局变量和静态变量 (在程序编译的时候内存已经分配好了, 比如数组地址是固定,const修饰)
动态存储区
堆区:用于程序动态分配 (malloc)
栈区:编译器自动分配,编译器自动申请和释放 2M, 比如:静态定义数组
void* malloc(size_t size)
分配内存的单元是 字节, 大小 size,连续的内存
如果申请失败,返回值是NULL
void* calloc(size_t _Count,size_t _Size)
申请 _Count 个 大小为_Size 的连续空间,这个连续空间的大小是_Size,如果申请失败,返回值是NULL
而不是 _Count * size, 同时,它会初始化为0
void *realloc(void *ptr, size_t size)
尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。
ptr -- 指针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
size -- 内存块的新的大小,以字节为单位。如果大小为 0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。
注意
: 动态申请的内存一定要收到释放
free(*p)
原则:不能重复释放
必须赋值NULL
申请和释放一一对应
- 看malloc的例子:
// 40.3M
//40M 手动分配,0.3M 哪来的? 程序的二进制文件
int const num = 100;
void mem() {
int *a = (int *)malloc(sizeof(int) * 1024 * 1024); 申请4M内存
}
int main()
{
// 4M 的数组
//栈溢出, 正常栈的大小是2M
int aa[1024 * 1024 * 10];
/*while (1) {
mem();
Sleep(2000);
}*/
int *a = (int *)malloc(sizeof(int) * 1024 * 1024);
if (a == NULL)
{
printf("内存不够,请温柔\n");
}
a[0] = 5;
printf("%d\n", sizeof(a));
printf("%#x", a);
a = (int *)malloc(sizeof(int) * 1024 * 1024* 2);
free(a);
a = NULL;
printf("a address: %#x,", a);
// 如果不进行a = NULL;那么,a 就是一个野指针
if (a != NULL) {
///
}
system("pause");
return 0;
}
输出结果:
- 看calloc的例子:
int main() {
int * b = (int *)calloc(4, 100 * sizeof(int));
if (b == NULL)
{
printf("内存不够,请温柔\n");
}
printf("%d\n", sizeof(b));
printf("%#x", b);
system("pause");
return 0;
}
输出结果:
字符串
C 语言没有String
用数组和指针表示
数组表示string char ch1[10] = {'c','h','i','n','a','\0'}; \0: 表示空格符 打印出来的是china,可以看出字符串上是使用 null 字符 '\0' 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
char ch1[10] = {'c','h','i','n','a','\0','a'}; 打印出来也是china, 因为编译器取的是'\0'之前的字符
Char * str = “china”;
等同于
char * c1 = (char )malloc(sizeof(char) * 20); c1 = "china"
说明:是否可以修改str[2] = b;
不能修改str[2] = ‘b’
把“abc”赋值给一个字符指针变量时,如: char ptr = “abc”;因为定义的是一个普通字符指针,
并没有定义空间来存放“abc”,所以编译器得帮我们先找个地方存放“abc”,显然,把这里的“abc”
当成常量并把它放到程序的常量区是编译器最合适的选择。所以的那个你去修改 char* ptr = “abc” 中的值,
如:ptr[0] = “g”的时候,会报错,因为这个地址里面存的是常量,常量是不能修改的。
Char *str = “Hello world”;
因为 “hello are you” 出现在一个表达式中时,“hello are you”使用的值就是这些字符所存储的地址(在常量区)。所以这个地址可以赋值给一个 char 类型的指针变量注意:“hello are you”作为字符数组初始化的时候就不是一个常量,其他情况下是一个常量。
char * str = "string";
分配一个常量区A:"string"
char * str 没有初始化
赋值:将常量区A的首地址赋值给str
char[] 和 char * 的区别
数组不能直接赋值, 需要借助其他的方法,如: strcpy
如果name是指针,可以直接赋值
c 语言双引号的字符串,约定成熟的都是放在常量区
- 例子:
#include "stdafx.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
//char ch1[10] = { 'c', 'h','i','n','a','\0','a'};
//char ch1[] = { 'c', 'h','i','n','a','\0' };
/*char ch1[20] = "china";
char a;
ch1[0] = 's';*/
/*char * ch = "china";*/
char * ch = (char *)malloc(100 * sizeof(char));
/*free(ch);
ch = NULL;*/
ch = "china";
strcpy(ch, "china");
ch[2] = 'b';
char * str = "string";
printf("%#x\n", str);
printf("%s\n", str);
system("pause");
return 0;
}
//字符串赋值和拼接
//int main()
//{
// char destination[25];
// char *blank = " ", *c = "C++", *Borland = "Borland";
// strcpy(destination, Borland);
// strcat(destination, blank); //strcat函数字符串拼接
// strcat(destination, c);
// printf("%s\n", destination);
// system("pause");
// return 0;
//
//}
结语
以上就是当前已记录的相关语法笔记,欢迎阅读和指正