一、typedef
1.可以起别名: typedef+原名+别名
struct person
{
char name[64];
int age;
};
typedef struct Person myPerson;
void test01()
{
struct Person p = {"aaa",10};
myPerson p2 = {"bbb",20};
}
2.区分数据类型
#include <stdio.h>
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
char *p1, p2;
int p3 = 0;
printf("p1数据类型是:%s\n", typeid(p1).name()); //char*
printf("p2数据类型是: %s\n", typeid(p2).name()); //char
printf("p3数据类型是: %s\n", typeid(p3).name());
system("pause");
return 0;
}
//typedef char * pchar
//pchar p1,p2 两个都是char*
3.提高移植性
typedef int myint;
void test()
{
myint a = 10;
}
//换类型就好,保证兼容性
二、void
1.修饰函数参数和返回
2.不可使用创建变量
3.void * 指针
//(1)由于void 指针没有特定的类型,因此可以指向任何类型的数据。因此他可以指向任何类型的数据。也就是说,任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换。
double d = 1.54;
void * p = & d;
//(2)虽然在 C 语言中,将 void* 指针赋值给其他指针类型是合法的,但在 C++ 中,需要进行显式的类型转换。这是为了确保类型安全性,避免隐藏的错误。可以使用 static_cast 进行显式的类型转换。
#include <stdio.h>
#include <windows.h>
int main()
{
void *p = NULL;
printf("void * = %d \n", sizeof(void *));
//32位机器是4位,64位机器是8位
int *pInt = NULL;
char *pChar = NULL;
pChar = static_cast<char *>(p);
//显式的类型转换
system("pause");
return 0;
}
//(3)避免对 void 指针进行算术操作
三、sizeof
不是一个函数,而是一个操作符
#include <stdio.h>
#include <windows.h>
int main()
{
char *a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(*a));
int b, c = 1;
printf("%d %d %d\n", sizeof(++b), 4, sizeof(int));
printf("%u\n %u\n", 0xFFFFFFFF, -1);
printf("%d\n %d\n", 0xFFFFFFFF, -1);
printf("%x\n %x\n", 0xFFFFFFFF, -1);
printf("%o\n %o\n", 0xFFFFFFFF, -1);
system("pause");
return 0;
}
四、变量的修改
1.间接修改
//(1)比较两个结构体之间的位置
#include <stdio.h>
#include <windows.h>
struct Person
{
char a;
int b;
char c;
int d;
};
int main()
{
struct Person p = {'a', 10, 'b', 20};
struct Person *pp = &p;
printf("%d\n", pp);
printf("%d\n", pp + 1);
system("pause");
return 0;
}
//输出结果的值相差16的原因:据默认的对齐规则,结构体变量中成员对齐,所以即使char本身是1个字节,但其后会自动补充一些字节来保证成员之间的对齐
//(2)修改变量值
int a = 100;
int *pInt = &a;
*pInt = 61;
printf("%d\n", a);
//(3)修改结构体成员变量的值
struct Person p = {'a', 10, 'b', 20};
struct Person *pp = &p;
printf("%d\n", pp);
printf("%d\n", pp + 1);
printf("%d\n", p.d); // 20
char *pst = (char *)pp;
*(int *)(pst + 12) = 2000;
printf("%d\n", p.d); // 2000
//char *pst = (char *)pp;
// *(int *)(pst + 12) = 2000;
//其中这两行代码先进行了一个指针的类型转换,而后再通过间接方式完成了对值的修改
//pst+12找到了最后一个int的d,后将原本char*的pst换成int*类型,再解引用修改值
五、内存分区
1.栈区(stack):存放函数形参和局部变量,由编译器自动分配和释放,先进后出,有限空间。
void add (int a,int b) //形参a,b
{
int c = 0; // 局部变量c
return a+b+c;
}
2.堆区(heap):该区由程序员申请后使用,需要手动释放否则会造成内存泄露。如果程序员没有手动释放,那么程序结束时可能由操作系统回收。先进先出,一般是malloc。
3.全局/静态存储区:存放全局变量和静态变量(包括静态全局变量与静态局部变量),
-
初始化的全局变量与静态局部变量放在一起(data段)
int a = 0; static int b = 10;
-
未初始化的放在一起(bss段)
int a; char *p;
4.文字常量区:常量在统一运行时被创建,常量区的内存是只读的,程序结束后由系统释放。
5.程序代码区: 存放程序的二进制代码,内存由系统管理。(4、5为text段)
-
text 和 data 段都在可执行文件中,由系统从可执行文件中加载;
-
而 bss 段不在可执行文件中,由系统初始化。
六、栈区
#define _CRY_SECURE_NO_WARNINGS //告诉编译器忽略与安全相关的警告信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
int *myFunc()
{
static int a = 10;
//原本使用int a = 10 时,会报错。因为此时的a是一个局部变量,它的生命周期仅限于该函数的执行过程。当函数返回时,其局部变量和栈帧都会被销毁。
//静态变量的生命周期会持续到程序的整个运行期间,并且在每次调用函数时,该变量的值都会保留
return &a;
}
void test()
{
int *p = myFunc();
printf("%d \n", *p);
}
int main()
{
test();
system("pause");
return 0;
}
//当函数返回时,其局部变量和栈帧都会被销毁。所以无法打印hello world
char *getString()
{
static char str[] = "hello world";
return str;
}
void test2()
{
char *p = NULL;
p = getString();
printf("%s\n", p);
}
//修改。考虑将 getString() 函数改为使用动态内存分配来存储字符串,并返回相应的指针
char *getString()
{
// static char str[] = "hello world";
// return str;
char *str = (char *)malloc(sizeof("hello world"));
strcpy(str, "hello world");
return str;
}
完整代码
#define _CRY_SECURE_NO_WARNINGS // 告诉编译器忽略与安全相关的警告信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
int *myFunc()
{
static int a = 10;
return &a;
}
char *getString()
{
// static char str[] = "hello world";
// return str;
char *str = (char *)malloc(sizeof("hello world"));
strcpy(str, "hello world");
return str;
}
void test()
{
int *p = myFunc();
printf("%d \n", *p);
}
void test2()
{
char *p = NULL;
p = getString();
printf("%s\n", p);
}
int main()
{
test();
test2();
system("pause");
return 0;
}
七、堆区
一般有malloc
#define _CRY_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
int *getSpace()
{
int *p = (int *)malloc(sizeof(int) * 5);
//此处的malloc函数的返回值其实是一个void* 需要转换指针类型
if (p == NULL)
{
return NULL;
}
for (int i = 0; i < 5; i++)
{
p[i] = i + 100;
}
return p;
}
void test()
{
int *p = getSpace();
//此处调用函数不会出现问题,因为有了malloc,堆中的数据会直到手动回收后消失
for (int i = 0; i < 5; i++)
{
printf("%d\n", p[i]);
}
}
int main()
{
test();
system("pause");
return 0;
}
八、堆栈注意
1.空指针赋值(错误示范及示意图)
void allocateSpace(char *pp)
{
char *temp = (char *)malloc(100);
memset(temp, 0, 100);
strcpy(temp, "hello world");
pp = temp;
}
void test2()
{
char *p = NULL;
allocateSpace(p);
printf("%s \n", p); //NULL
}
2.成功代码(二级指针)
void allocateSpace(char **pp)
{
char *temp = (char *)malloc(100);
memset(temp, 0, 100);
strcpy(temp, "hello world");
*pp = temp;
}
void test2()
{
char *p = NULL;
allocateSpace(&p);
printf("%s \n", p);
}
补充:memset()函数
void *memset(void *s, int c, size_t n)函数
-
s指向要填充的内存块。
-
c是要被设置的值。
-
n是要被设置该值的字符数。
-
返回类型是一个指向存储区s的指针。
九、Static and Extern
1.static 静态变量
特点:在运行前分配内存(活)。程序运行结束则生命周期结束。本文件中都可以使用。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
static int a = 10;//全局静态
void test1()
{
//局部静态
static int b = 20;
//与a的生命周期相同,但作用域不同
}
int main()
{
printf("%d\n",a);
system("pause");
return 0;
}
2.extern 外部变量
extern int g_a; //告诉编译器不要报错,g_a是一个外部链接属性
十、常量
1.用const修饰的不能修改
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
const int a = 10;
void test1()
{
int *p = &a; //受系统保护无法直接用&a取
*p = 100;
printf("%d\n",a);
}
void test2()
{
const int b =20;
int *p = &b; // 受系统保护无法直接用&b取
*p = 30;
printf("%d\n",b);
}
int main()
{
test1();
system("pause");
return 0;
}
2.字符串常量
void test3()//指向同一地址
{
char *p1 = "aaa";
char *p2 = "aaa";
char *p3 = "aaa";
printf("%d\n%d\n%d\n", p1, p2, p3);
}
//不能够直接修改字符,且尽量不要直接修改字符串常量
void test3()
{
char *p1 = "aaa";
char *p2 = "aaa";
char *p3 = "aaa";
char *str = "acd";
// str[0] = "x";
printf("%c\n", str[0]);
printf("%d\n%d\n%d\n", p1, p2, p3);
}