学习c++ Part02
- 前言
- 1.函数
- 注意点:
- 全局函数(默认函数)
- 静态函数
- 2.预处理
- 2.1 变量
- 3.头文件
- 4.宏函数
- 5.指针
- 5.1 普通变量与指针变量建立关系:
- 5.2 指针初始化
- 5.3 指针变量的注意事项
- 5.3.1 void 不能定义普通变量,void * 可以定义指针变量
- 5.3.2 指针变量未初始化不要取* ,初始化NULL不要取 * (段错误)
- 5.3.3 指针变量 不要越界
- 5.4 数组元素指针
- 定义:指针只是指向数组第一个元素的地址
- 数组元素的指针变量和数组等价
- 在使用中 【】就是 *()的缩写
- 指向同一个数组元素的两个指针变量的关系
- 5.5 字符串指针
- 自我理解:字符串指针域数值指针的不同
- "xxx" 可以表示字符窜 也可以表示"xxx"字符串的首元素地址
- 因为字符串放在文字常量去不可写 不能赋值
- 5.6 数值的指针数组
- 5.7 字符指针数组
- 5.8 二维字符数组
- 5.9 指针的指针
- n级指针变量可以保存n-1级指针变量的地址
- 5.9 指针数组和数组指针
- 5.10 数组与数组指针的关系
- n维数组与n-1维数组指针是完全等价的
- 数组在作为函数参数时会被编译器自动优化成数组指针(64平台8B),n维数组优化成n-1维数组指针
- 5.11 多维数组在物理上 都是一维存储
- 6.指针与函数
- 6.1 指针变量作为函数的参数
- 6.2 函数的返回值类型为指针
- 6.3 函数指针
- 函数指针变量的注意事项
- 函数指针使用typedef定义
- 函数指针的使用目的(函数指针作为参数,类似于“多态”实现)
前言
时间:2023年6月29日 - 7月3日(这几天懈怠了检讨下,学习语言就需要速战速决!)
1.函数
函数的定义声明调用都与Java类似。
需要注意的是:如果调用在定义函数之前那么需要对函数进行提前声明,声明的格式与java中的抽象函数书写类似。
代码示例:
#include <iostream>
using namespace std;
//提前声明函数
void getIntArray(int arr[5], int n);
void sortIntArray(int arr[5], int n);
void printIntArray(int arr[5], int n);
int main() {
//定义数组
int arr[5] = {0};
int n = sizeof(arr)/sizeof(arr[0]);
//获取键盘输入的数组
getIntArray(arr,n);
//对数组进行排序
sortIntArray(arr,n);
//打印数组
printIntArray(arr,n);
return 0;
}
void printIntArray(int arr[5], int n) {
for (int i = 0; i < n; ++i) {
cout<<arr[i]<<" ";
}
}
void sortIntArray(int arr[5], int n) {
//冒泡排序
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n - i - 1; ++j) {
if(arr[j]>arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
void getIntArray(int arr[5], int n) {
cout<<"intput 5 numbers:";
for (int i = 0; i < n; ++i) {
cin>>arr[i];
}
}
注意点:
- 调用在定义之前,需要对方法进行提前声明
- 普通变量,调用函数不能对其进行修改,引用变量(如数组、对象…)可以进行修改
void test(int num){
num++;
}
int main(){
int num = 10;
test(num);
cout<<num; //结果仍然为10,不是11
}
全局函数(默认函数)
- 其他源文件都可以使用
- 谁用谁extern声明
静态函数
- 只能在当前源文件使用
2.预处理
进程:可执行文件 从运行到结束 整个动态过程 叫作进程
32位平台。每个进程分配4G的虚拟空间,64 8G?
内存:
(只读)文字常量区:数值常量、字符常量、字符窜常量、符号常量
(只读)代码区:代码的二进制指令
(&可读可写)栈区:局部变量、函数的形参、返回值<48
(&可读可写)全局区:普通全局变量、静态局部变量、静态全局变量
(可读可写)堆区:使用malloc、calloc、realloc、free动态申请
2.1 变量
- 普通局部变量(栈区)
- 作用范围和生命周期在{ }有效
- 不初始化 内容随机 ;局部同名 就近原则
- 普通全局变量(全局区)
- 当前和其他源文件都有效 生命周期整个进程
- 不初始化 内容为0;与局部变量同名优先选择局部变量
- 其他文件使用全局变量时,需要extern声明(谁使用谁声明)
- 静态局部变量(全局区)
- 作用范围:所在的{}复合语句之间有效 ;生命周期:整个进程有效
- 不初始化内容为0;整个进程都存在(第一次定义有效)
void fun04()
{
static int num=10;
num++;
cout<<"num = "<<num<<endl;
}
int main() {
fun04();//num = 11
fun04();//num = 12
fun04();//num = 13
fun04();//num = 14
}
- 静态全局变量(全局区)
- 作用范围:只能在当前源文件中进行使用 生命周期:整个进程
- 不初始化内容为0
3.头文件
#include <head.h> //建议使用此形式 从系统目录中寻找
#include "head.h" //不建议使用此形式 优先从当前目录中寻找,找不到再从系统目录中寻找
4.宏函数
- 编译四阶段:预处理、编译、汇编、链接
- 不带参数的宏定义
#define PI 3.14
#define MY_STR "hello world"
#define N 100
- 带参数的宏定义
#define MY_MUL(a, b) a*b //宏不能有数据类型
cout<<MY_MUL(10,20);//10*20
- 结束宏的作用域
- 使用#undef
- 注意事项:
- 宏不能有参数类型
- 宏不能保证参数的完整性,可以通过()来保证参数完整性
- 宏的作用范围:从定义开始到当前文件结束都有效
- 宏不能构成结构体、类的成员
- 宏函数与普通函数的区别:
- 宏函数不需要进栈出栈,调用多少次就会展开多少次,用空间换时间
- 带参函数调用的时候需要进栈出栈,所以说节省了空间浪费了时间
5.指针
在32位平台,系统为每一个字节分配32的地址编号,编号称为地址,指针变量也是4字节大小
5.1 普通变量与指针变量建立关系:
int num = 10;
int *p = # //&num 表示的num地址
cout<<*p; // 10(指向地址的内容)
cout<<p // 32位地址
// *p等价于num; p等价于&num
说法:
✅ 指针p指向了num
✅ 指针p保存了num地址
❎ 指针p指向了num地址
5.2 指针初始化
- 如果不初始化 立即操作 会出现错误
建议初始化为NULL
int *p = NULL;
- 要学会判断指针变量的类型
//将指针名字去掉就是指针类型
//知道如何判断指针类型,可用于赋值判断语句
int num = 10 ;
int *p = #
//num 为 int ; &num 为 int * --> 对变量名取地址->整体加*
//p 为 int * ; *p 为 int --> 指针变量取* -> 整体减*
*&p == p
- 指针变量指向类型
将*和指针名去掉就是指向类型 int *p = &num 指向的就是int类型
- 指针变量的指向类型决定了指针的指向宽度
- 指针变量的指向类型决定了+1的跨度
int *p1 = # //宽度为4字节 +1的跨度为4字节
short *p2 = # //宽度为2字节 +1的跨度为2字节
char *p3 = # //宽度为1字节 +1的跨度为1字节
案例了练习:
int num = 0x01020304;
//案例1 :取出0x0102的值
short *p1 = (short *)#
*(p+1);
//案例2:取出0x02的值
char * p2 = (char *)#
*(p+2);
//案例3:取出0x0203的值
char * p3 = (char *)#
*(short *)(p+1);
5.3 指针变量的注意事项
5.3.1 void 不能定义普通变量,void * 可以定义指针变量
// void num: ❎
void *p ; //✅
- p是万能的一级指针,如果用于函数形参可以达到操作多种数据类型的目的
- 不要对void类型指针不能取※操作
int num = 10;
void *p = #
*p;//err p指向的类型为void 无法确定p的取值宽度 所以不能*p
5.3.2 指针变量未初始化不要取* ,初始化NULL不要取 * (段错误)
5.3.3 指针变量 不要越界
char ch = 'a';
int *p = &ch;
*p;//error 越界访问非法内存
5.4 数组元素指针
定义:指针只是指向数组第一个元素的地址
int arr[4] = {10,20,30,40};
int *p = &arr[0]
int *p = arr // arr = &arr[0]
// 也可以单独指向某个位置的元素
int *p = arr[2];
数组元素的指针变量和数组等价
*(p+1) = arr[1];
int arr[4] = {10,20,30,40};
int *p = arr;
*(p+1) = arr[1];
在使用中 【】就是 *()的缩写
int arr[5] = {10, 20, 30, 40, 50};
arr[1] = *(arr+1) = 1[arr] //20
- 为啥arr = &arr[0]
&arr[0] == &*(arr+0) == arr+0 == arr
指向同一个数组元素的两个指针变量的关系
5.5 字符串指针
自我理解:字符串指针域数值指针的不同
字符串指针,指向的是字符串首元素的地址,而整个字符串放在了文字常量区(只读)可以通过首地址取到全部,这也就是为什么不需要加*也能取到整个字符串的原因
数值指针,则需要通过加* 来获取到指向地址的内容数值
图示:
“xxx” 可以表示字符窜 也可以表示"xxx"字符串的首元素地址
int *str = "hello world";
cout<<str; // hello world
cout<<*str; //h
因为字符串放在文字常量去不可写 不能赋值
int *str = "hello world";
str[6] = 'W' // 错误 权限冲突不可写
5.6 数值的指针数组
代码示例:
int num1 = 10;
int num2 = 20;
int num3 = 30;
int num4 = 40;
int *arr[4] = {&num1, &num2, &num3, &num4};
int n = sizeof(arr)/sizeof(arr[0]);
for(int i=0;i<n;i++)
{
cout<<*arr[i]<<" ";//10 20 30 40
}
cout<<endl;
5.7 字符指针数组
代码示例:
char *arr[4] = {"hahaha","hehehe","lalala","xixixi"};
int n = sizeof(arr)/sizeof(arr[0]);
for(int i = 0 ; i < n ; i++){
cout<<arr[i]<<" ";
}
5.8 二维字符数组
char *arr1[4]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};
char arr2[4][128]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};
arr1是在指针数组 存放的是每个字符串的首元素的地址
arr2是二维字符数组 存放的是每个字符串
5.9 指针的指针
n级指针变量可以保存n-1级指针变量的地址
int num = 10;
int *p = #
int **q = &p;
cout<<*p //10
cout<<**q // 10
5.9 指针数组和数组指针
指针数组与数组指针:
int *arr[5];//指针数组 本质是数组 每个元素为int *
int (*arr)[5];//数组指针 本质是指针变量 保存的是数组的首地址(概数组必须5个元素每个元素为 int)
数组指针的定义方式:
int arr[5] = {10,20,30,40,50};
int (*p)[5] = &arr;
- &arr == p 代表的是数组首地址
- *p == *&arr == arr 代表的是 数组的首元素地址(即第一行的首元素地址)
- *p+1 == *&arr+1 == arr + 1 代表的是第一行1号位元素的地址
- *(p+1) 代表第二行首元素的地址
- *(* (p+1)) 代表第二行首元素的地址中的内容
5.10 数组与数组指针的关系
n维数组与n-1维数组指针是完全等价的
int arr[n]; int *p;
int arr[n][m]; int (*p)[m]
int arr[n][m][k]; int (*p)[m][k]
n维数组 和n-1维的数组指针 等价
数组在作为函数参数时会被编译器自动优化成数组指针(64平台8B),n维数组优化成n-1维数组指针
5.11 多维数组在物理上 都是一维存储
int arr[3][4]={{1,2,3,4}, {5,6,7,8},{9,10,11,12}};
int row = sizeof(arr)/sizeof(arr[0]);
int col = sizeof(arr[0])/sizeof(arr[0][0]);
int *p = &arr[0][0];
int i=0;
for(i=0;i<row*col;i++)
{
cout<<p[i]<<" ";
}
cout<<endl;
//输出结果:1-12
6.指针与函数
6.1 指针变量作为函数的参数
函数内部不能修改外部变量的值,但是当将外部变量的地址作为参数传入函数,函数则可以对该变量进行修改
void setNum02(int *p)//int *p=#
{
//*p == num
*p = 100;
}
void test01()
{
int num =0;
setNum02(&num);//单向传递之 传地址
cout<<"num = "<<num<<endl;//100 修改成功
}
6.2 函数的返回值类型为指针
将函数内部参数的合法地址,作为返回值,给外部使用。
注意:函数返回的是static修饰的静态局部变量的地址
为什么:如果是普通局部变量,在{}函数调用完毕后会进行释放,那么返回的指针指向的是一个非法地址
int* getAddr(void)
{
//int data = 100;//不要返回普通局部变量的地址
static int data = 100;
return &data;
}
void test04()
{
int *p = NULL;
p = getAddr();
cout<<"*p = "<<*p<<endl;//100
}
6.3 函数指针
函数指针,本质是指针变量,保存的是函数的入口地址,64位平台占用8B
定义&调用示例:
int myAdd(int x,int y){ return x+y;}
//方式1:
//int (*p)(int,int) = NULL; //指针尽量初始化
//p = myadd;
//方式2:
int (*p)(int,int) = myadd;
//调用(传入实参)
cout<<p(10,20) <<endl;// 30
函数指针变量的注意事项
- 函数指针变量 不要+1 无意义
- 不要对函数指针变量取* 无意义(*p会被编译器优化成p)
- 函数指针变量 判断大小 > < 无意义
- 函数指针变量 可以赋值 p2=p1
- 函数指针变量 可以判断相等 p2 ==p1
函数指针使用typedef定义
int myAdd(int x,int y){ return x+y;}
typedef int (*FUN_TYPE)(int,int);
FUN_TYPE p = myAdd;
函数指针的使用目的(函数指针作为参数,类似于“多态”实现)
目的:让函数功能多样化(相当于参入不同的函数指针,调用不同的方法)
案例:设计一个算法实现 加减乘除
int myAdd(int x,int y) {return x+y; }
int mySub(int x,int y) { return x-y;}
int myMul(int x,int y) {return x*y;}
int myDiv(int x,int y) {return x/y; }
//设计算法 操作上面的函数
int myCalc(int x,int y, int (*func)(int,int) )
{return func(x,y);}
void test()
{
//int (*func)(int,int) = myAdd;
cout<<myCalc(10,20, myAdd)<<endl;//30
cout<<myCalc(10,20, mySub)<<endl;//-10
cout<<myCalc(10,20, myMul)<<endl;//200
cout<<myCalc(10,20, myDiv)<<endl;//0
}